From 9cd6e0a83635dce6de97965b7b4cdd7e6c9099da Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Dec 2019 21:36:19 +0100 Subject: [PATCH 01/25] First draft --- frame/treasury/src/lib.rs | 313 +++++++++++++++++++++++++++++--------- 1 file changed, 244 insertions(+), 69 deletions(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 121432fd057a2..76eb95e765422 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -60,13 +60,13 @@ #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; use sp_std::prelude::*; -use frame_support::{decl_module, decl_storage, decl_event, ensure, print, decl_error}; +use frame_support::{decl_module, decl_storage, decl_event, ensure, print, decl_error, Parameter}; use frame_support::traits::{ - Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, + Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, ExistenceRequirement::AllowDeath, ReservableCurrency, WithdrawReason }; -use sp_runtime::{Permill, ModuleId}; -use sp_runtime::traits::{Zero, EnsureOrigin, StaticLookup, AccountIdConversion, Saturating}; +use sp_runtime::{Permill, ModuleId, Percent, RuntimeDebug}; +use sp_runtime::traits::{Zero, EnsureOrigin, StaticLookup, AccountIdConversion, Saturating, Hash}; use frame_support::weights::SimpleDispatchInfo; use codec::{Encode, Decode}; use frame_system::{self as system, ensure_signed}; @@ -87,6 +87,24 @@ pub trait Trait: frame_system::Trait { /// Origin from which rejections must come. type RejectOrigin: EnsureOrigin; + /// Origin from which tippers must come. + type TipOrigin: EnsureOrigin; + + /// The number of tippers a tipping proposal must get before it will happen. + type TipThreshold: Get; + + /// The period for which a tip remains open after is has achieved `TipThreshold` tippers. + type TipCountdown: Get; + + /// The amount of the final tip which goes to the original reporter of the tip. + type TipFindersFee: Get; + + /// The amount held on deposit for placing a tip report. + type TipReportDepositBase: Get>; + + /// The amount held on deposit per byte within the tip report reason. + type TipReportDepositPerByte: Get>; + /// The overarching event type. type Event: From> + Into<::Event>; @@ -109,6 +127,113 @@ pub trait Trait: frame_system::Trait { type ProposalIndex = u32; +/// A spending proposal. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct Proposal { + proposer: AccountId, + value: Balance, + beneficiary: AccountId, + bond: Balance, +} + +/// An open tipping "motion". +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] +pub struct OpenTip< + AccountId: Parameter, + Balance: Parameter, + BlockNumber: Parameter, +> { + /// The reason for the tip. Should be a human-readable UTF-8 encoded string. A URL would be + /// sensible. + reason: Vec, + /// The account to be tipped. + who: AccountId, + /// The account who began this tip and the amount held on deposit. + finder: Option<(AccountId, Balance)>, + /// The block number at which this tip will close if `Some`. If `None`, then no closing is + /// scheduled. + closes: Option, + /// The members who have voted for this tip. Sorted by AccountId. + tips: Vec<(AccountId, Balance)>, +} + +decl_storage! { + trait Store for Module as Treasury { + /// Number of proposals that have been made. + ProposalCount get(fn proposal_count): ProposalIndex; + + /// Proposals that have been made. + Proposals get(fn proposals): map ProposalIndex => Option>>; + + /// Proposal indices that have been approved but not yet awarded. + Approvals get(fn approvals): Vec; + + /// Tips that are not yet completed. Keyed by the hash of `(reason, who)` from the value. + pub Tips get(fn tips): map hasher(twox_64_concat) T::Hash + => Option, T::BlockNumber>>; + } + add_extra_genesis { + build(|_config| { + // Create Treasury account + let _ = T::Currency::make_free_balance_be( + &>::account_id(), + T::Currency::minimum_balance(), + ); + }); + } +} + +decl_event!( + pub enum Event + where + Balance = BalanceOf, + ::AccountId, + ::Hash, + { + /// New proposal. + Proposed(ProposalIndex), + /// We have ended a spend period and will now allocate funds. + Spending(Balance), + /// Some funds have been allocated. + Awarded(ProposalIndex, Balance, AccountId), + /// A proposal was rejected; funds were slashed. + Rejected(ProposalIndex, Balance), + /// Some of our funds have been burnt. + Burnt(Balance), + /// Spending has finished; this is the amount that rolls over until next spend. + Rollover(Balance), + /// Some funds have been deposited. + Deposit(Balance), + /// A new tip suggestion has been opened. + NewTip(Hash), + /// A tip suggestion has reached threshold and . + TipClosing(Hash), + /// A new tip suggestion has been opened. + TipClosed(Hash, AccountId, Balance), + /// A new tip suggestion has been opened. + TipRetracted(Hash), + } +); + +decl_error! { + /// Error for the treasury module. + pub enum Error for Module { + /// Proposer's balance is too low. + InsufficientProposersBalance, + /// No proposal at that index. + InvalidProposalIndex, + /// The reason give is just too big. + ReasonTooBig, + /// The tip was already found/started. + AlreadyKnown, + /// The tip hash is unknown. + UnknownTip, + /// The account attempting to retract the tip is not the finder of the tip. + NotFinder, + } +} + decl_module! { pub struct Module for enum Call where origin: T::Origin { /// Fraction of a proposal's value that should be bonded in order to place the proposal. @@ -128,6 +253,80 @@ decl_module! { fn deposit_event() = default; + fn report_awesome(origin, reason: Vec, who: T::AccountId) { + let finder = ensure_signed(origin)?; + + const MAX_SENSIBLE_REASON_LENGTH: usize = 1024; + ensure!(reason.len() <= MAX_SENSIBLE_REASON_LENGTH, Error::::ReasonTooBig); + + let hash = T::Hashing::hash_of(&(&reason, &who)); + ensure!(!Tips::::exists(&hash), Error::::AlreadyKnown); + + let deposit = T::TipReportDepositBase::get() + + T::TipReportDepositPerByte::get() * (reason.len() as u32).into(); + T::Currency::reserve(&finder, deposit)?; + + let finder = Some((finder, deposit)); + Tips::::insert(&hash, OpenTip { reason, who, finder, closes: None, tips: vec![] }); + Self::deposit_event(RawEvent::NewTip(hash)); + } + + fn retract_tip(origin, hash: T::Hash) { + let who = ensure_signed(origin)?; + + let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; + let (finder, deposit) = tip.finder.ok_or(Error::::NotFinder)?; + ensure!(finder == who, Error::::NotFinder); + Tips::::remove(&hash); + let _ = T::Currency::unreserve(&who, deposit); + Self::deposit_event(RawEvent::TipRetracted(hash)); + } + + /// Give a tip for something new. + fn tip_new(origin, reason: Vec, who: T::AccountId, tip_value: BalanceOf) { + let tipper = T::TipOrigin::ensure_origin(origin)?; + + let hash = T::Hashing::hash_of(&(&reason, &who)); + let tip = if let Some(mut tip) = Tips::::get(hash) { + if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) { + Self::deposit_event(RawEvent::TipClosing(hash.clone())); + } + tip + } else { + Self::deposit_event(RawEvent::NewTip(hash.clone())); + OpenTip { reason, who, finder: None, closes: None, tips: vec![(tipper, tip_value)] } + }; + Tips::::insert(&hash, tip); + } + + /// Add onto an existing tip. + fn tip(origin, hash: T::Hash, tip_value: BalanceOf) { + let tipper = T::TipOrigin::ensure_origin(origin)?; + + let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) { + Self::deposit_event(RawEvent::TipClosing(hash.clone())); + } + Tips::::insert(&hash, tip); + } + + /// Close and payout a tip. + /// + /// Doesn't matter who calls this. + fn close_tip(origin, hash: T::Hash) { + ensure_signed(origin)?; + + if let Some(tip) = Tips::::get(&hash) { + if let Some(n) = tip.closes.as_ref() { + if system::Module::::block_number() >= *n { + // closed. + Self::payout_tip(tip); + Tips::::remove(hash); + } + } + } + } + /// Put forward a suggestion for spending. A deposit proportional to the value /// is reserved and slashed if the proposal is rejected. It is returned once the /// proposal is awarded. @@ -202,71 +401,6 @@ decl_module! { } } -/// A spending proposal. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, PartialEq, Eq, sp_runtime::RuntimeDebug)] -pub struct Proposal { - proposer: AccountId, - value: Balance, - beneficiary: AccountId, - bond: Balance, -} - -decl_storage! { - trait Store for Module as Treasury { - /// Number of proposals that have been made. - ProposalCount get(fn proposal_count): ProposalIndex; - - /// Proposals that have been made. - Proposals get(fn proposals): map ProposalIndex => Option>>; - - /// Proposal indices that have been approved but not yet awarded. - Approvals get(fn approvals): Vec; - } - add_extra_genesis { - build(|_config| { - // Create Treasury account - let _ = T::Currency::make_free_balance_be( - &>::account_id(), - T::Currency::minimum_balance(), - ); - }); - } -} - -decl_event!( - pub enum Event - where - Balance = BalanceOf, - ::AccountId - { - /// New proposal. - Proposed(ProposalIndex), - /// We have ended a spend period and will now allocate funds. - Spending(Balance), - /// Some funds have been allocated. - Awarded(ProposalIndex, Balance, AccountId), - /// A proposal was rejected; funds were slashed. - Rejected(ProposalIndex, Balance), - /// Some of our funds have been burnt. - Burnt(Balance), - /// Spending has finished; this is the amount that rolls over until next spend. - Rollover(Balance), - /// Some funds have been deposited. - Deposit(Balance), - } -); - -decl_error! { - /// Error for the treasury module. - pub enum Error for Module { - /// Proposer's balance is too low. - InsufficientProposersBalance, - /// No proposal at that index. - InvalidProposalIndex, - } -} - impl Module { // Add public immutables and private mutables. @@ -283,6 +417,47 @@ impl Module { T::ProposalBondMinimum::get().max(T::ProposalBond::get() * value) } + /// Given a mutable reference to an `OpenTip`, insert the tip into it and check whether it + /// closes, if so, then deposit the relevant event and set closing accordingly. + fn insert_tip_and_check_closing( + tip: &mut OpenTip, T::BlockNumber>, + tipper: T::AccountId, + tip_value: BalanceOf, + ) -> bool { + match tip.tips.binary_search_by_key(&&tipper, |x| &x.0) { + Ok(pos) => tip.tips.insert(pos, (tipper, tip_value)), + Err(pos) => tip.tips[pos] = (tipper, tip_value), + } + if tip.tips.len() >= T::TipThreshold::get() as usize && tip.closes.is_none() { + tip.closes = Some(system::Module::::block_number() + T::TipCountdown::get()); + true + } else { + false + } + } + + /// Execute the payout of a tip. + fn payout_tip(tip: OpenTip, T::BlockNumber>) { + let mut tips = tip.tips; + tips.sort_by_key(|i| i.1); + let treasury = Self::account_id(); + let max_payout = T::Currency::free_balance(&treasury); + let mut payout = tips[tips.len() / 2].1.min(max_payout); + if let Some((finder, deposit)) = tip.finder { + let _ = T::Currency::unreserve(&finder, deposit); + if finder != tip.who { + // pay out the finder's fee. + let finders_fee = T::TipFindersFee::get() * payout; + payout -= finders_fee; + // this should go through given we checked it's at most the free balance, but still + // we only make a best-effort. + let _ = T::Currency::transfer(&treasury, &finder, deposit, AllowDeath); + } + } + // same as above: best-effort only. + let _ = T::Currency::transfer(&treasury, &tip.who, payout, AllowDeath); + } + // Spend some money! fn spend_funds() { let mut budget_remaining = Self::pot(); From 31a1e38972336c9208ca1d5a259bc3cfa69a4b32 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Dec 2019 00:06:45 +0100 Subject: [PATCH 02/25] Initial work on tests --- frame/treasury/src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 76eb95e765422..777ed3ccda047 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -542,6 +542,7 @@ mod tests { use super::*; use frame_support::{assert_noop, assert_ok, impl_outer_origin, parameter_types, weights::Weight}; + use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, OnFinalize, IdentityLookup}, testing::Header, Perbill @@ -598,11 +599,18 @@ mod tests { pub const ProposalBondMinimum: u64 = 1; pub const SpendPeriod: u64 = 2; pub const Burn: Permill = Permill::from_percent(50); + pub const TipOrigin: Vec = vec![10, 11, 12, 13, 14]; } impl Trait for Test { type Currency = pallet_balances::Module; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; + type TipOrigin: EnsureSignedBy; + type TipThreshold: Get; + type TipCountdown: Get; + type TipFindersFee: Get; + type TipReportDepositBase: Get>; + type TipReportDepositPerByte: Get>; type Event = (); type ProposalRejection = (); type ProposalBond = ProposalBond; @@ -632,6 +640,15 @@ mod tests { }); } + #[test] + fn basic_tipping_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_eq!(Treasury::pot(), 100); + }); + } + #[test] fn minting_works() { new_test_ext().execute_with(|| { From 20eef24b6fb96b121edd929a3552d1b031dd84b0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 27 Dec 2019 11:22:29 +0000 Subject: [PATCH 03/25] Add tests. --- bin/node/runtime/Cargo.toml | 8 +- frame/treasury/src/lib.rs | 187 +++++++++++++++++++++++++++++++----- 2 files changed, 169 insertions(+), 26 deletions(-) diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 33cb7b61dbd5c..a8f26d13cba40 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -31,6 +31,10 @@ sp-transaction-pool = { version = "2.0.0", default-features = false, path = "../ sp-version = { version = "2.0.0", default-features = false, path = "../../../primitives/version" } # frame dependencies +frame-executive = { version = "2.0.0", default-features = false, path = "../../../frame/executive" } +frame-support = { version = "2.0.0", default-features = false, path = "../../../frame/support" } +frame-system = { version = "2.0.0", default-features = false, path = "../../../frame/system" } +frame-system-rpc-runtime-api = { version = "2.0.0", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } pallet-authority-discovery = { version = "2.0.0", default-features = false, path = "../../../frame/authority-discovery" } pallet-authorship = { version = "2.0.0", default-features = false, path = "../../../frame/authorship" } pallet-babe = { version = "2.0.0", default-features = false, path = "../../../frame/babe" } @@ -40,7 +44,6 @@ pallet-contracts = { version = "2.0.0", default-features = false, path = "../../ pallet-contracts-rpc-runtime-api = { version = "2.0.0", default-features = false, path = "../../../frame/contracts/rpc/runtime-api/" } pallet-democracy = { version = "2.0.0", default-features = false, path = "../../../frame/democracy" } pallet-elections-phragmen = { version = "2.0.0", default-features = false, path = "../../../frame/elections-phragmen" } -frame-executive = { version = "2.0.0", default-features = false, path = "../../../frame/executive" } pallet-finality-tracker = { version = "2.0.0", default-features = false, path = "../../../frame/finality-tracker" } pallet-grandpa = { version = "2.0.0", default-features = false, path = "../../../frame/grandpa" } pallet-im-online = { version = "2.0.0", default-features = false, path = "../../../frame/im-online" } @@ -53,9 +56,6 @@ pallet-session = { version = "2.0.0", features = ["historical"], path = "../../. pallet-staking = { version = "2.0.0", features = ["migrate"], path = "../../../frame/staking", default-features = false } pallet-staking-reward-curve = { version = "2.0.0", path = "../../../frame/staking/reward-curve" } pallet-sudo = { version = "2.0.0", default-features = false, path = "../../../frame/sudo" } -frame-support = { version = "2.0.0", default-features = false, path = "../../../frame/support" } -frame-system = { version = "2.0.0", default-features = false, path = "../../../frame/system" } -frame-system-rpc-runtime-api = { version = "2.0.0", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } pallet-timestamp = { version = "2.0.0", default-features = false, path = "../../../frame/timestamp" } pallet-treasury = { version = "2.0.0", default-features = false, path = "../../../frame/treasury" } pallet-utility = { version = "2.0.0", default-features = false, path = "../../../frame/utility" } diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 777ed3ccda047..e4678b38ad708 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -231,6 +231,10 @@ decl_error! { UnknownTip, /// The account attempting to retract the tip is not the finder of the tip. NotFinder, + /// The tip cannot be claimed/closed because there are not enough tippers yet. + StillOpen, + /// The tip cannot be claimed/closed because it's still in the countdown period. + Premature, } } @@ -316,15 +320,12 @@ decl_module! { fn close_tip(origin, hash: T::Hash) { ensure_signed(origin)?; - if let Some(tip) = Tips::::get(&hash) { - if let Some(n) = tip.closes.as_ref() { - if system::Module::::block_number() >= *n { - // closed. - Self::payout_tip(tip); - Tips::::remove(hash); - } - } - } + let tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + let n = tip.closes.as_ref().ok_or(Error::::StillOpen)?; + ensure!(system::Module::::block_number() >= *n, Error::::Premature); + // closed. + Self::payout_tip(tip); + Tips::::remove(hash); } /// Put forward a suggestion for spending. A deposit proportional to the value @@ -425,8 +426,8 @@ impl Module { tip_value: BalanceOf, ) -> bool { match tip.tips.binary_search_by_key(&&tipper, |x| &x.0) { - Ok(pos) => tip.tips.insert(pos, (tipper, tip_value)), - Err(pos) => tip.tips[pos] = (tipper, tip_value), + Ok(pos) => tip.tips[pos] = (tipper, tip_value), + Err(pos) => tip.tips.insert(pos, (tipper, tip_value)), } if tip.tips.len() >= T::TipThreshold::get() as usize && tip.closes.is_none() { tip.closes = Some(system::Module::::block_number() + T::TipCountdown::get()); @@ -451,7 +452,7 @@ impl Module { payout -= finders_fee; // this should go through given we checked it's at most the free balance, but still // we only make a best-effort. - let _ = T::Currency::transfer(&treasury, &finder, deposit, AllowDeath); + let _ = T::Currency::transfer(&treasury, &finder, finders_fee, AllowDeath); } } // same as above: best-effort only. @@ -542,10 +543,10 @@ mod tests { use super::*; use frame_support::{assert_noop, assert_ok, impl_outer_origin, parameter_types, weights::Weight}; - use frame_system::EnsureSignedBy; + use frame_support::traits::Contains; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, OnFinalize, IdentityLookup}, testing::Header, Perbill + traits::{BlakeTwo256, OnFinalize, IdentityLookup, BadOrigin}, testing::Header, Perbill }; impl_outer_origin! { @@ -594,23 +595,33 @@ mod tests { type TransferFee = TransferFee; type CreationFee = CreationFee; } + pub struct TenToFourteen; + impl Contains for TenToFourteen { + fn contains(n: &u64) -> bool { + *n >= 10 && *n <= 14 + } + } parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const ProposalBondMinimum: u64 = 1; pub const SpendPeriod: u64 = 2; pub const Burn: Permill = Permill::from_percent(50); - pub const TipOrigin: Vec = vec![10, 11, 12, 13, 14]; + pub const TipThreshold: u32 = 3; + pub const TipCountdown: u64 = 1; + pub const TipFindersFee: Percent = Percent::from_percent(20); + pub const TipReportDepositBase: u64 = 1; + pub const TipReportDepositPerByte: u64 = 1; } impl Trait for Test { type Currency = pallet_balances::Module; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; - type TipOrigin: EnsureSignedBy; - type TipThreshold: Get; - type TipCountdown: Get; - type TipFindersFee: Get; - type TipReportDepositBase: Get>; - type TipReportDepositPerByte: Get>; + type TipOrigin = frame_system::EnsureSignedBy; + type TipThreshold = TipThreshold; + type TipCountdown = TipCountdown; + type TipFindersFee = TipFindersFee; + type TipReportDepositBase = TipReportDepositBase; + type TipReportDepositPerByte = TipReportDepositPerByte; type Event = (); type ProposalRejection = (); type ProposalBond = ProposalBond; @@ -618,6 +629,7 @@ mod tests { type SpendPeriod = SpendPeriod; type Burn = Burn; } + type System = frame_system::Module; type Balances = pallet_balances::Module; type Treasury = Module; @@ -641,11 +653,142 @@ mod tests { } #[test] - fn basic_tipping_works() { + fn tip_new_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::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Treasury::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Treasury::tip_new(Origin::signed(12), b"awesome.dot".to_vec(), 3, 10)); + + assert_noop!(Treasury::tip_new(Origin::signed(9), b"awesome.dot".to_vec(), 3, 10), BadOrigin); + + System::set_block_number(2); + let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + }); + } + + #[test] + fn report_awesome_and_tip_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::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + assert_eq!(Balances::reserved_balance(&0), 12); + assert_eq!(Balances::free_balance(&0), 88); + + // other reports don't count. + assert_noop!( + Treasury::report_awesome(Origin::signed(1), b"awesome.dot".to_vec(), 3), + Error::::AlreadyKnown + ); + + let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Treasury::tip(Origin::signed(9), h.clone(), 10), BadOrigin); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::signed(100), h.into())); + assert_eq!(Balances::reserved_balance(&0), 0); + assert_eq!(Balances::free_balance(&0), 102); + assert_eq!(Balances::free_balance(&3), 8); + }); + } + + #[test] + fn report_awesome_from_beneficiary_and_tip_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::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 0)); + assert_eq!(Balances::reserved_balance(&0), 12); + assert_eq!(Balances::free_balance(&0), 88); + let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &0u64)); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::signed(100), h.into())); + assert_eq!(Balances::reserved_balance(&0), 0); + assert_eq!(Balances::free_balance(&0), 110); + }); + } + + #[test] + fn close_tip_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_eq!(Treasury::pot(), 100); + + assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::StillOpen); + + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::Premature); + + System::set_block_number(2); + assert_noop!(Treasury::close_tip(Origin::NONE, h.into()), BadOrigin); + assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + assert_eq!(Balances::free_balance(&3), 10); + + assert_noop!(Treasury::close_tip(Origin::signed(100), h.into()), Error::::UnknownTip); + }); + } + + #[test] + fn retract_tip_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::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Treasury::retract_tip(Origin::signed(10), h.clone()), Error::::NotFinder); + assert_ok!(Treasury::retract_tip(Origin::signed(0), h.clone())); + System::set_block_number(2); + assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::UnknownTip); + }); + } + + #[test] + fn tip_median_calculation_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::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); + let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 1000000)); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + assert_eq!(Balances::free_balance(&3), 10); + }); + } + + #[test] + fn tip_changing_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::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); + let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10000)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10000)); + assert_ok!(Treasury::tip(Origin::signed(13), h.clone(), 0)); + assert_ok!(Treasury::tip(Origin::signed(14), h.clone(), 0)); + assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 1000)); + assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 100)); + assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); + System::set_block_number(2); + assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + assert_eq!(Balances::free_balance(&3), 10); }); } From e645eeacf4d8679c1b8dd35fae370bc11cfb385e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 27 Dec 2019 12:53:47 +0000 Subject: [PATCH 04/25] Ensure old members can't tip. --- frame/elections-phragmen/src/lib.rs | 8 +++++++- frame/support/src/traits.rs | 6 ------ frame/treasury/src/lib.rs | 22 ++++++++++++++-------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 8e937756498f5..f2fdfe47df162 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -88,7 +88,7 @@ use frame_support::{ decl_storage, decl_event, ensure, decl_module, weights::SimpleDispatchInfo, traits::{ Currency, Get, LockableCurrency, LockIdentifier, ReservableCurrency, WithdrawReasons, - ChangeMembers, OnUnbalanced, WithdrawReason + ChangeMembers, OnUnbalanced, WithdrawReason, Contains } }; use sp_phragmen::ExtendedBalance; @@ -731,6 +731,12 @@ impl Module { } } +impl Contains for Module { + fn contains(who: &T::AccountId) -> bool { + Self::is_member(who) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 379f964d271a0..8b80002575f5d 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -58,12 +58,6 @@ pub trait Contains { fn contains(t: &T) -> bool; } -impl> Contains for T { - fn contains(t: &V) -> bool { - &Self::get() == t - } -} - /// The account with the given id was killed. #[impl_trait_for_tuples::impl_for_tuples(30)] pub trait OnFreeBalanceZero { diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index e4678b38ad708..65e672c546db7 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -65,9 +65,10 @@ use frame_support::traits::{ Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, ExistenceRequirement::AllowDeath, ReservableCurrency, WithdrawReason }; -use sp_runtime::{Permill, ModuleId, Percent, RuntimeDebug}; -use sp_runtime::traits::{Zero, EnsureOrigin, StaticLookup, AccountIdConversion, Saturating, Hash}; -use frame_support::weights::SimpleDispatchInfo; +use sp_runtime::{Permill, ModuleId, Percent, RuntimeDebug, traits::{ + Zero, EnsureOrigin, StaticLookup, AccountIdConversion, Saturating, Hash, BadOrigin +}}; +use frame_support::{weights::SimpleDispatchInfo, traits::Contains}; use codec::{Encode, Decode}; use frame_system::{self as system, ensure_signed}; @@ -88,7 +89,7 @@ pub trait Trait: frame_system::Trait { type RejectOrigin: EnsureOrigin; /// Origin from which tippers must come. - type TipOrigin: EnsureOrigin; + type Tippers: Contains; /// The number of tippers a tipping proposal must get before it will happen. type TipThreshold: Get; @@ -137,7 +138,8 @@ pub struct Proposal { bond: Balance, } -/// An open tipping "motion". +/// An open tipping "motion". Retains all details of a tip including information on the finder +/// and the members who have voted. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] pub struct OpenTip< AccountId: Parameter, @@ -288,7 +290,8 @@ decl_module! { /// Give a tip for something new. fn tip_new(origin, reason: Vec, who: T::AccountId, tip_value: BalanceOf) { - let tipper = T::TipOrigin::ensure_origin(origin)?; + let tipper = ensure_signed(origin)?; + ensure!(T::Tippers::contains(&tipper), BadOrigin); let hash = T::Hashing::hash_of(&(&reason, &who)); let tip = if let Some(mut tip) = Tips::::get(hash) { @@ -305,7 +308,8 @@ decl_module! { /// Add onto an existing tip. fn tip(origin, hash: T::Hash, tip_value: BalanceOf) { - let tipper = T::TipOrigin::ensure_origin(origin)?; + let tipper = ensure_signed(origin)?; + ensure!(T::Tippers::contains(&tipper), BadOrigin); let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) { @@ -429,6 +433,7 @@ impl Module { Ok(pos) => tip.tips[pos] = (tipper, tip_value), Err(pos) => tip.tips.insert(pos, (tipper, tip_value)), } + tip.tips.retain(|(ref a, _)| T::Tippers::contains(a)); if tip.tips.len() >= T::TipThreshold::get() as usize && tip.closes.is_none() { tip.closes = Some(system::Module::::block_number() + T::TipCountdown::get()); true @@ -440,6 +445,7 @@ impl Module { /// Execute the payout of a tip. fn payout_tip(tip: OpenTip, T::BlockNumber>) { let mut tips = tip.tips; + tips.retain(|(ref a, _)| T::Tippers::contains(a)); tips.sort_by_key(|i| i.1); let treasury = Self::account_id(); let max_payout = T::Currency::free_balance(&treasury); @@ -616,7 +622,7 @@ mod tests { type Currency = pallet_balances::Module; type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; - type TipOrigin = frame_system::EnsureSignedBy; + type Tippers = TenToFourteen; type TipThreshold = TipThreshold; type TipCountdown = TipCountdown; type TipFindersFee = TipFindersFee; From a9c184d0331b16c31336f031dc81dd0dd0a9a4a9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jan 2020 15:50:55 +0000 Subject: [PATCH 05/25] Fix complexity --- frame/democracy/src/lib.rs | 3 + frame/elections-phragmen/src/lib.rs | 1 + frame/support/src/traits.rs | 5 +- frame/treasury/src/lib.rs | 214 +++++++++++++++++++++++----- 4 files changed, 183 insertions(+), 40 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 61d1dc285e4ab..e861ec2b0c40b 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1232,6 +1232,9 @@ mod tests { fn contains(n: &u64) -> bool { *n >= 1 && *n <= 5 } + fn members() -> Vec { + vec![1, 2, 3, 4, 5] + } } thread_local! { static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 298aae2e54a81..55d6892a2fe09 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -771,6 +771,7 @@ impl Contains for Module { fn contains(who: &T::AccountId) -> bool { Self::is_member(who) } + fn sorted_members() -> Vec { Self::members_ids() } } #[cfg(test)] diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 8b80002575f5d..cfbe53390a698 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -56,6 +56,9 @@ impl Get for () { pub trait Contains { /// Return `true` if this "contains" the given value `t`. fn contains(t: &T) -> bool; + + /// Get a vector of all members in the set, orderred. + fn sorted_members() -> Vec; } /// The account with the given id was killed. @@ -91,7 +94,7 @@ impl FindAuthor for () { /// A trait for verifying the seal of a header and returning the author. pub trait VerifySeal { - /// Verify a header and return the author, if any. + /// Verify a header and r;-89-9;~eturn the author, if any. fn verify_seal(header: &Header) -> Result, &'static str>; } diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 65e672c546db7..5e414f9bf55b0 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -76,6 +76,7 @@ type BalanceOf = <::Currency as Currency< = <::Currency as Currency<::AccountId>>::PositiveImbalance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; +/// The treasury's module id, used for deriving its sovereign account ID. const MODULE_ID: ModuleId = ModuleId(*b"py/trsry"); pub trait Trait: frame_system::Trait { @@ -126,15 +127,20 @@ pub trait Trait: frame_system::Trait { type Burn: Get; } -type ProposalIndex = u32; +/// An index of a proposal. Just a `u32`. +pub type ProposalIndex = u32; /// A spending proposal. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] pub struct Proposal { + /// The account proposing it. proposer: AccountId, + /// The (total) amount that should be pad if the proposal is accepted. value: Balance, + /// The account to whom the payment should be made if the proposal is accepted. beneficiary: AccountId, + /// The amount held on deposit (reserved) for making this proposal. bond: Balance, } @@ -145,10 +151,11 @@ pub struct OpenTip< AccountId: Parameter, Balance: Parameter, BlockNumber: Parameter, + Hash: Parameter, > { /// The reason for the tip. Should be a human-readable UTF-8 encoded string. A URL would be /// sensible. - reason: Vec, + reason: Hash, /// The account to be tipped. who: AccountId, /// The account who began this tip and the amount held on deposit. @@ -172,8 +179,13 @@ decl_storage! { Approvals get(fn approvals): Vec; /// Tips that are not yet completed. Keyed by the hash of `(reason, who)` from the value. + /// This has the insecure enumerable hash function since the key itself is already + /// guaranteed to be a secure hash. pub Tips get(fn tips): map hasher(twox_64_concat) T::Hash - => Option, T::BlockNumber>>; + => Option, T::BlockNumber, T::Hash>>; + + /// Simple preimage lookup from the reason's hash to the original data. + pub Reasons get(fn reasons): map hasher(twox_64_concat) T::Hash => Option>; } add_extra_genesis { build(|_config| { @@ -255,58 +267,148 @@ decl_module! { /// Percentage of spare funds (if any) that are burnt per spend period. const Burn: Permill = T::Burn::get(); + /// The number of tippers a tipping proposal must get before it will happen. + const TipThreshold: u32 = T::TipThreshold::get(); + + /// The period for which a tip remains open after is has achieved `TipThreshold` tippers. + const TipCountdown: T::BlockNumber = T::TipCountdown::get(); + + /// The amount of the final tip which goes to the original reporter of the tip. + const TipFindersFee: Percent = T::TipFindersFee::get(); + + /// The amount held on deposit for placing a tip report. + const TipReportDepositBase: BalanceOf = T::TipReportDepositBase::get(); + + /// The amount held on deposit per byte within the tip report reason. + const TipReportDepositPerByte: BalanceOf = T::TipReportDepositPerByte::get(); + type Error = Error; fn deposit_event() = default; + /// Report something `reason` that deserves a tip and claim any eventual the finder's fee. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Payment: `TipReportDepositBase` will be reserved from the origin account, as well as + /// `TipReportDepositPerByte` for each byte in `reason`. + /// + /// - `reason`: The reason for, or the thing that deserves, the tip; generally this will be + /// a UTF-8-encoded URL. + /// - `who`: The account which should be credited for the tip. + /// + /// Emits `NewTip` if successful. + /// + /// # + /// - `O(R)` where `R` length of `reason`. + /// - One balance operation. + /// - One storage mutation (codec `O(R)`). + /// - One event. + /// # fn report_awesome(origin, reason: Vec, who: T::AccountId) { let finder = ensure_signed(origin)?; - const MAX_SENSIBLE_REASON_LENGTH: usize = 1024; + const MAX_SENSIBLE_REASON_LENGTH: usize = 16384; ensure!(reason.len() <= MAX_SENSIBLE_REASON_LENGTH, Error::::ReasonTooBig); - let hash = T::Hashing::hash_of(&(&reason, &who)); + let reason_hash = T::Hashing::hash(&reason[..]); + ensure!(!Reasons::::exists(&reason_hash), Error::::AlreadyKnown); + let hash = T::Hashing::hash_of(&(&reason_hash, &who)); ensure!(!Tips::::exists(&hash), Error::::AlreadyKnown); let deposit = T::TipReportDepositBase::get() + T::TipReportDepositPerByte::get() * (reason.len() as u32).into(); T::Currency::reserve(&finder, deposit)?; + Reasons::::insert(&reason_hash, &reason); let finder = Some((finder, deposit)); - Tips::::insert(&hash, OpenTip { reason, who, finder, closes: None, tips: vec![] }); + let tip = OpenTip { reason: reason_hash, who, finder, closes: None, tips: vec![] }; + Tips::::insert(&hash, tip); Self::deposit_event(RawEvent::NewTip(hash)); } + /// Report something `reason` that deserves a tip and claim any eventual the finder's fee. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// Payment: `TipReportDepositBase` will be reserved from the origin account, as well as + /// `TipReportDepositPerByte` for each byte in `reason`. + /// + /// - `reason`: The reason for, or the thing that deserves, the tip; generally this will be + /// a UTF-8-encoded URL. + /// - `who`: The account which should be credited for the tip. + /// + /// Emits `NewTip` if successful. + /// + /// # + /// - `O(R)` where `R` length of `reason`. + /// - One balance operation. + /// - One storage mutation (codec `O(R)`). + /// - One event. + /// # fn retract_tip(origin, hash: T::Hash) { let who = ensure_signed(origin)?; let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; let (finder, deposit) = tip.finder.ok_or(Error::::NotFinder)?; ensure!(finder == who, Error::::NotFinder); + Reasons::::remove(&tip.reason); Tips::::remove(&hash); let _ = T::Currency::unreserve(&who, deposit); Self::deposit_event(RawEvent::TipRetracted(hash)); } - /// Give a tip for something new. + /// Give a tip for something new; no finder's fee will be taken. + /// + /// The dispatch origin for this call must be _Signed_ and the signing account must be a + /// member of the `Tippers` set. + /// + /// - `reason`: The reason for, or the thing that deserves, the tip; generally this will be + /// a UTF-8-encoded URL. + /// - `who`: The account which should be credited for the tip. + /// - `tip_value`: The amount of tip that the sender would like to give. The median tip + /// value of active tippers will be given to the `who`. + /// + /// Emits `NewTip` if successful. + /// + /// # + /// - `O(R)` where `R` length of `reason`. + /// - One storage mutation (codec `O(R)`). + /// - One event. + /// # fn tip_new(origin, reason: Vec, who: T::AccountId, tip_value: BalanceOf) { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); - - let hash = T::Hashing::hash_of(&(&reason, &who)); - let tip = if let Some(mut tip) = Tips::::get(hash) { - if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) { - Self::deposit_event(RawEvent::TipClosing(hash.clone())); - } - tip - } else { - Self::deposit_event(RawEvent::NewTip(hash.clone())); - OpenTip { reason, who, finder: None, closes: None, tips: vec![(tipper, tip_value)] } - }; + let reason_hash = T::Hashing::hash(&reason[..]); + ensure!(!Reasons::::exists(&reason_hash), Error::::AlreadyKnown); + let hash = T::Hashing::hash_of(&(&reason_hash, &who)); + + Reasons::::insert(&reason_hash, &reason); + Self::deposit_event(RawEvent::NewTip(hash.clone())); + let tips = vec![(tipper, tip_value)]; + let tip = OpenTip { reason: reason_hash, who, finder: None, closes: None, tips }; Tips::::insert(&hash, tip); } - /// Add onto an existing tip. + /// Declare a tip value for an already-open tip. + /// + /// The dispatch origin for this call must be _Signed_ and the signing account must be a + /// member of the `Tippers` set. + /// + /// - `hash`: The identity of the open tip for which a tip value is declared. This is formed + /// as the hash of the tuple of the hash of the original tip `reason` and the beneficiary + /// account ID. + /// - `tip_value`: The amount of tip that the sender would like to give. The median tip + /// value of active tippers will be given to the `who`. + /// + /// Emits `TipClosing` if the threshold of tippers has been reached and the countdown period + /// has started. + /// + /// # + /// - `O(R)` where `R` is the original length of `reason`. + /// - One storage mutation (codec `O(R)`). + /// - Up to one event. + /// # fn tip(origin, hash: T::Hash, tip_value: BalanceOf) { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); @@ -320,7 +422,18 @@ decl_module! { /// Close and payout a tip. /// - /// Doesn't matter who calls this. + /// The dispatch origin for this call must be _Signed_. + /// + /// The tip identified by `hash` must have finished its countdown period. + /// + /// - `hash`: The identity of the open tip for which a tip value is declared. This is formed + /// as the hash of the tuple of the original tip `reason` and the beneficiary account ID. + /// + /// # + /// - `O(R)` where `R` is the original length of `reason`. + /// - One storage retrieval and removal (codec `O(R)`). + /// - Up to three balance operations. + /// # fn close_tip(origin, hash: T::Hash) { ensure_signed(origin)?; @@ -328,8 +441,9 @@ decl_module! { let n = tip.closes.as_ref().ok_or(Error::::StillOpen)?; ensure!(system::Module::::block_number() >= *n, Error::::Premature); // closed. - Self::payout_tip(tip); + Reasons::::remove(&tip.reason); Tips::::remove(hash); + Self::payout_tip(tip); } /// Put forward a suggestion for spending. A deposit proportional to the value @@ -425,7 +539,7 @@ impl Module { /// Given a mutable reference to an `OpenTip`, insert the tip into it and check whether it /// closes, if so, then deposit the relevant event and set closing accordingly. fn insert_tip_and_check_closing( - tip: &mut OpenTip, T::BlockNumber>, + tip: &mut OpenTip, T::BlockNumber, T::Hash>, tipper: T::AccountId, tip_value: BalanceOf, ) -> bool { @@ -443,9 +557,28 @@ impl Module { } /// Execute the payout of a tip. - fn payout_tip(tip: OpenTip, T::BlockNumber>) { + /// + /// Up to three balance operations. + /// Plus `O(T)` (`T` is Tippers length). + fn payout_tip(tip: OpenTip, T::BlockNumber, T::Hash>) { let mut tips = tip.tips; - tips.retain(|(ref a, _)| T::Tippers::contains(a)); + let members = T::Tippers::sorted_members(); + let mut members_iter = members.iter(); + let mut member = members_iter.next(); + tips.retain(|(ref a, _)| loop { + match member { + None => break false, + Some(m) if m > a => break false, + Some(m) => { + member = members_iter.next(); + if m < a { + continue + } else { + break true; + } + } + } + }); tips.sort_by_key(|i| i.1); let treasury = Self::account_id(); let max_payout = T::Currency::free_balance(&treasury); @@ -606,6 +739,9 @@ mod tests { fn contains(n: &u64) -> bool { *n >= 10 && *n <= 14 } + fn sorted_members() -> Vec { + vec![10, 11, 12, 13, 14] + } } parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); @@ -658,20 +794,20 @@ mod tests { }); } + fn tip_hash() -> H256 { + BlakeTwo256::hash_of(&(BlakeTwo256::hash(b"awesome.dot"), 3u64)) + } + #[test] - fn tip_new_works() { + fn tip_new_cannot_be_used_twice() { 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::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); - assert_ok!(Treasury::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10)); - assert_ok!(Treasury::tip_new(Origin::signed(12), b"awesome.dot".to_vec(), 3, 10)); - - assert_noop!(Treasury::tip_new(Origin::signed(9), b"awesome.dot".to_vec(), 3, 10), BadOrigin); - - System::set_block_number(2); - let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); - assert_ok!(Treasury::close_tip(Origin::signed(0), h.into())); + assert_noop!( + Treasury::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10), + Error::::AlreadyKnown + ); }); } @@ -690,7 +826,7 @@ mod tests { Error::::AlreadyKnown ); - let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + let h = tip_hash(); assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); @@ -711,7 +847,7 @@ mod tests { assert_ok!(Treasury::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 0)); assert_eq!(Balances::reserved_balance(&0), 12); assert_eq!(Balances::free_balance(&0), 88); - let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &0u64)); + let h = BlakeTwo256::hash_of(&(BlakeTwo256::hash(b"awesome.dot"), 0u64)); assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); @@ -730,7 +866,7 @@ mod tests { assert_eq!(Treasury::pot(), 100); assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); - let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + let h = tip_hash(); assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); assert_noop!(Treasury::close_tip(Origin::signed(0), h.into()), Error::::StillOpen); @@ -752,7 +888,7 @@ mod tests { // Check that accumulate works when we have Some value in Dummy already. Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Treasury::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); - let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + let h = tip_hash(); assert_ok!(Treasury::tip(Origin::signed(10), h.clone(), 10)); assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10)); @@ -769,7 +905,7 @@ mod tests { // Check that accumulate works when we have Some value in Dummy already. Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); - let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + let h = tip_hash(); assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10)); assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 1000000)); System::set_block_number(2); @@ -784,7 +920,7 @@ mod tests { // Check that accumulate works when we have Some value in Dummy already. Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_ok!(Treasury::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); - let h = BlakeTwo256::hash_of(&(&b"awesome.dot".to_vec(), &3u64)); + let h = tip_hash(); assert_ok!(Treasury::tip(Origin::signed(11), h.clone(), 10000)); assert_ok!(Treasury::tip(Origin::signed(12), h.clone(), 10000)); assert_ok!(Treasury::tip(Origin::signed(13), h.clone(), 0)); From ff776b26636023612f5515e03d56f1f4942f3f63 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jan 2020 18:39:00 +0000 Subject: [PATCH 06/25] Update node runtime --- bin/node/runtime/src/lib.rs | 14 ++++++++++++-- frame/support/src/traits.rs | 3 +++ frame/treasury/src/lib.rs | 33 +++++++++++++++------------------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 679bad2371749..9498e451e39dc 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -29,7 +29,9 @@ use frame_support::{ use sp_core::u32_trait::{_1, _2, _3, _4}; use node_primitives::{AccountId, AccountIndex, Balance, BlockNumber, Hash, Index, Moment, Signature}; use sp_api::impl_runtime_apis; -use sp_runtime::{Permill, Perbill, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str}; +use sp_runtime::{ + Permill, Perbill, Percent, ApplyExtrinsicResult, impl_opaque_keys, generic, create_runtime_str +}; use sp_runtime::curve::PiecewiseLinear; use sp_runtime::transaction_validity::TransactionValidity; use sp_runtime::traits::{ @@ -376,7 +378,11 @@ parameter_types! { pub const ProposalBondMinimum: Balance = 1 * DOLLARS; pub const SpendPeriod: BlockNumber = 1 * DAYS; pub const Burn: Permill = Permill::from_percent(50); -} + pub const TipCountdown: BlockNumber = 1 * DAYS; + pub const TipFindersFee: Percent = Percent::from_percent(20); + pub const TipReportDepositBase: Balance = 1 * DOLLARS; + pub const TipReportDepositPerByte: Balance = 1 * CENTS; +}} impl pallet_treasury::Trait for Runtime { type Currency = Balances; @@ -388,6 +394,10 @@ impl pallet_treasury::Trait for Runtime { type ProposalBondMinimum = ProposalBondMinimum; type SpendPeriod = SpendPeriod; type Burn = Burn; + type TipCountdown = TipCountdown; + type TipFindersFee = TipFindersFee; + type TipReportDepositBase = TipReportDepositBase; + type TipReportDepositPerByte = TipReportDepositPerByte; } parameter_types! { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index cfbe53390a698..d10ba4f092f4b 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -59,6 +59,9 @@ pub trait Contains { /// Get a vector of all members in the set, orderred. fn sorted_members() -> Vec; + + /// Get the number of items in the set. + fn count() -> usize { Self::sorted_members().len() } } /// The account with the given id was killed. diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 5e414f9bf55b0..76a12fac0c684 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -92,10 +92,7 @@ pub trait Trait: frame_system::Trait { /// Origin from which tippers must come. type Tippers: Contains; - /// The number of tippers a tipping proposal must get before it will happen. - type TipThreshold: Get; - - /// The period for which a tip remains open after is has achieved `TipThreshold` tippers. + /// The period for which a tip remains open after is has achieved threshold tippers. type TipCountdown: Get; /// The amount of the final tip which goes to the original reporter of the tip. @@ -267,10 +264,7 @@ decl_module! { /// Percentage of spare funds (if any) that are burnt per spend period. const Burn: Permill = T::Burn::get(); - /// The number of tippers a tipping proposal must get before it will happen. - const TipThreshold: u32 = T::TipThreshold::get(); - - /// The period for which a tip remains open after is has achieved `TipThreshold` tippers. + /// The period for which a tip remains open after is has achieved threshold tippers. const TipCountdown: T::BlockNumber = T::TipCountdown::get(); /// The amount of the final tip which goes to the original reporter of the tip. @@ -547,8 +541,9 @@ impl Module { Ok(pos) => tip.tips[pos] = (tipper, tip_value), Err(pos) => tip.tips.insert(pos, (tipper, tip_value)), } - tip.tips.retain(|(ref a, _)| T::Tippers::contains(a)); - if tip.tips.len() >= T::TipThreshold::get() as usize && tip.closes.is_none() { + Self::retain_active_tips(&mut tip.tips); + let threshold = (T::Tippers::count() + 1) / 2; + if tip.tips.len() >= threshold && tip.closes.is_none() { tip.closes = Some(system::Module::::block_number() + T::TipCountdown::get()); true } else { @@ -556,12 +551,7 @@ impl Module { } } - /// Execute the payout of a tip. - /// - /// Up to three balance operations. - /// Plus `O(T)` (`T` is Tippers length). - fn payout_tip(tip: OpenTip, T::BlockNumber, T::Hash>) { - let mut tips = tip.tips; + fn retain_active_tips(tips: &mut Vec<(T::AccountId, BalanceOf)>) { let members = T::Tippers::sorted_members(); let mut members_iter = members.iter(); let mut member = members_iter.next(); @@ -579,6 +569,15 @@ impl Module { } } }); + } + + /// Execute the payout of a tip. + /// + /// Up to three balance operations. + /// Plus `O(T)` (`T` is Tippers length). + fn payout_tip(tip: OpenTip, T::BlockNumber, T::Hash>) { + let mut tips = tip.tips; + Self::retain_active_tips(&mut tips); tips.sort_by_key(|i| i.1); let treasury = Self::account_id(); let max_payout = T::Currency::free_balance(&treasury); @@ -748,7 +747,6 @@ mod tests { pub const ProposalBondMinimum: u64 = 1; pub const SpendPeriod: u64 = 2; pub const Burn: Permill = Permill::from_percent(50); - pub const TipThreshold: u32 = 3; pub const TipCountdown: u64 = 1; pub const TipFindersFee: Percent = Percent::from_percent(20); pub const TipReportDepositBase: u64 = 1; @@ -759,7 +757,6 @@ mod tests { type ApproveOrigin = frame_system::EnsureRoot; type RejectOrigin = frame_system::EnsureRoot; type Tippers = TenToFourteen; - type TipThreshold = TipThreshold; type TipCountdown = TipCountdown; type TipFindersFee = TipFindersFee; type TipReportDepositBase = TipReportDepositBase; From d37868ee14931c86cb7518eb490ac0f1294fc710 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jan 2020 18:39:20 +0000 Subject: [PATCH 07/25] Build fix. --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9498e451e39dc..ec6634b87e1c0 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -382,7 +382,7 @@ parameter_types! { pub const TipFindersFee: Percent = Percent::from_percent(20); pub const TipReportDepositBase: Balance = 1 * DOLLARS; pub const TipReportDepositPerByte: Balance = 1 * CENTS; -}} +} impl pallet_treasury::Trait for Runtime { type Currency = Balances; From 287e74f05c435d794edfe44a6b9b28f154b2b6e9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jan 2020 18:43:47 +0000 Subject: [PATCH 08/25] build fix --- bin/node/runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ec6634b87e1c0..fa353b8ad1324 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -394,6 +394,7 @@ impl pallet_treasury::Trait for Runtime { type ProposalBondMinimum = ProposalBondMinimum; type SpendPeriod = SpendPeriod; type Burn = Burn; + type Tippers = Elections; type TipCountdown = TipCountdown; type TipFindersFee = TipFindersFee; type TipReportDepositBase = TipReportDepositBase; From 6a179a6a9e9b1d748f317be1fddc08e795845bbf Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 2 Jan 2020 19:00:08 +0000 Subject: [PATCH 09/25] Fix tests --- frame/membership/src/lib.rs | 16 +++++++++++++++- frame/support/src/traits.rs | 4 ++-- frame/system/src/lib.rs | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 3a65f1604eb6e..80779f15a457f 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -228,10 +228,11 @@ mod tests { use std::cell::RefCell; use frame_support::{assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight}; + use frame_support::traits::Contains; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried. - use sp_runtime::{Perbill, traits::{BlakeTwo256, IdentityLookup}, testing::Header, traits::BadOrigin}; + use sp_runtime::{Perbill, traits::{BlakeTwo256, IdentityLookup, BadOrigin}, testing::Header}; use frame_system::EnsureSignedBy; impl_outer_origin! { @@ -275,6 +276,19 @@ mod tests { pub const Five: u64 = 5; } + impl Contains for One { + fn sorted_members() -> Vec { vec![1] } + } + impl Contains for Two { + fn sorted_members() -> Vec { vec![2] } + } + impl Contains for Three { + fn sorted_members() -> Vec { vec![3] } + } + impl Contains for Four { + fn sorted_members() -> Vec { vec![4] } + } + thread_local! { static MEMBERS: RefCell> = RefCell::new(vec![]); } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index d10ba4f092f4b..53a3ad189cac1 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -53,9 +53,9 @@ impl Get for () { /// 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. -pub trait Contains { +pub trait Contains { /// Return `true` if this "contains" the given value `t`. - fn contains(t: &T) -> bool; + fn contains(t: &T) -> bool { Self::sorted_members().binary_search(t).is_ok() } /// Get a vector of all members in the set, orderred. fn sorted_members() -> Vec; diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 903523fdf82c5..89fa10d07f9a6 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -471,7 +471,7 @@ pub struct EnsureSignedBy(sp_std::marker::PhantomData<(Who, Acco impl< O: Into, O>> + From>, Who: Contains, - AccountId: PartialEq + Clone, + AccountId: PartialEq + Clone + Ord, > EnsureOrigin for EnsureSignedBy { type Success = AccountId; fn try_origin(o: O) -> Result { From da826aa814d26eda1cae646f0f21a112e9a202c5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 3 Jan 2020 11:56:07 +0000 Subject: [PATCH 10/25] Fix tests --- frame/democracy/src/lib.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index e861ec2b0c40b..d539e70a41ac5 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1229,13 +1229,35 @@ mod tests { } pub struct OneToFive; impl Contains for OneToFive { - fn contains(n: &u64) -> bool { - *n >= 1 && *n <= 5 - } - fn members() -> Vec { + fn sorted_members() -> Vec { vec![1, 2, 3, 4, 5] } } + impl Contains for One { + fn sorted_members() -> Vec { + vec![1] + } + } + impl Contains for Two { + fn sorted_members() -> Vec { + vec![2] + } + } + impl Contains for Three { + fn sorted_members() -> Vec { + vec![3] + } + } + impl Contains for Four { + fn sorted_members() -> Vec { + vec![4] + } + } + impl Contains for Five { + fn sorted_members() -> Vec { + vec![5] + } + } thread_local! { static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); } From ae9d70bb3b68ae986a828da0408ff58b733dd63c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 3 Jan 2020 12:14:37 +0000 Subject: [PATCH 11/25] Refactor Contains impl for tests --- frame/democracy/src/lib.rs | 25 ------------------------- frame/membership/src/lib.rs | 13 ------------- frame/support/src/traits.rs | 8 ++++++++ 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index d539e70a41ac5..b427263d22683 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1233,31 +1233,6 @@ mod tests { vec![1, 2, 3, 4, 5] } } - impl Contains for One { - fn sorted_members() -> Vec { - vec![1] - } - } - impl Contains for Two { - fn sorted_members() -> Vec { - vec![2] - } - } - impl Contains for Three { - fn sorted_members() -> Vec { - vec![3] - } - } - impl Contains for Four { - fn sorted_members() -> Vec { - vec![4] - } - } - impl Contains for Five { - fn sorted_members() -> Vec { - vec![5] - } - } thread_local! { static PREIMAGE_BYTE_DEPOSIT: RefCell = RefCell::new(0); } diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 80779f15a457f..2bed4862330e3 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -276,19 +276,6 @@ mod tests { pub const Five: u64 = 5; } - impl Contains for One { - fn sorted_members() -> Vec { vec![1] } - } - impl Contains for Two { - fn sorted_members() -> Vec { vec![2] } - } - impl Contains for Three { - fn sorted_members() -> Vec { vec![3] } - } - impl Contains for Four { - fn sorted_members() -> Vec { vec![4] } - } - thread_local! { static MEMBERS: RefCell> = RefCell::new(vec![]); } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 53a3ad189cac1..e4092529a62ab 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -64,6 +64,14 @@ pub trait Contains { fn count() -> usize { Self::sorted_members().len() } } +impl> Contains for G { + fn contains(t: &T) -> bool { &Self::get() == t } + fn sorted_members() -> Vec { + vec![Self::get()] + } + fn count() -> usize { 1 } +} + /// The account with the given id was killed. #[impl_trait_for_tuples::impl_for_tuples(30)] pub trait OnFreeBalanceZero { From 4e3195a67563b4ebb8b9dfb2894d451aa19f939f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 3 Jan 2020 18:32:28 +0000 Subject: [PATCH 12/25] Introduce new way to avoid impl Contains conflicts --- frame/democracy/src/lib.rs | 5 +++-- frame/identity/src/lib.rs | 7 ++++++- frame/membership/src/lib.rs | 7 +++++-- frame/nicks/src/lib.rs | 7 ++++++- frame/support/src/lib.rs | 25 +++++++++++++++++++++++++ frame/support/src/traits.rs | 10 +--------- frame/treasury/src/lib.rs | 2 +- 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index b427263d22683..6c596b2119cce 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1144,8 +1144,7 @@ mod tests { use std::cell::RefCell; use frame_support::{ impl_outer_origin, impl_outer_dispatch, assert_noop, assert_ok, parameter_types, - traits::Contains, - weights::Weight, + ord_parameter_types, traits::Contains, weights::Weight, }; use sp_core::H256; use sp_runtime::{ @@ -1221,6 +1220,8 @@ mod tests { pub const MinimumDeposit: u64 = 1; pub const EnactmentPeriod: u64 = 2; pub const CooloffPeriod: u64 = 2; + } + ord_parameter_types! { pub const One: u64 = 1; pub const Two: u64 = 2; pub const Three: u64 = 3; diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 1e98e60b5e6b9..0070d8c8fcd4a 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -866,7 +866,10 @@ mod tests { use super::*; use sp_runtime::traits::BadOrigin; - use frame_support::{assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight}; + use frame_support::{ + assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight, + ord_parameter_types + }; use sp_core::H256; use frame_system::EnsureSignedBy; // The testing primitives are very useful for avoiding having to work with signatures @@ -929,6 +932,8 @@ mod tests { pub const FieldDeposit: u64 = 10; pub const SubAccountDeposit: u64 = 10; pub const MaximumSubAccounts: u32 = 2; + } + ord_parameter_types! { pub const One: u64 = 1; pub const Two: u64 = 2; } diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 2bed4862330e3..bc1bb4ab2053c 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -227,7 +227,10 @@ mod tests { use super::*; use std::cell::RefCell; - use frame_support::{assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight}; + use frame_support::{ + assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight, + ord_parameter_types + }; use frame_support::traits::Contains; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures @@ -268,7 +271,7 @@ mod tests { type Version = (); type ModuleToIndex = (); } - parameter_types! { + ord_parameter_types! { pub const One: u64 = 1; pub const Two: u64 = 2; pub const Three: u64 = 3; diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index 997bf74392849..573d44de58b17 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -241,7 +241,10 @@ decl_module! { mod tests { use super::*; - use frame_support::{assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight}; + use frame_support::{ + assert_ok, assert_noop, impl_outer_origin, parameter_types, weights::Weight, + ord_parameter_types + }; use sp_core::H256; use frame_system::EnsureSignedBy; // The testing primitives are very useful for avoiding having to work with signatures @@ -303,6 +306,8 @@ mod tests { pub const ReservationFee: u64 = 2; pub const MinLength: usize = 3; pub const MaxLength: usize = 16; + } + ord_parameter_types! { pub const One: u64 = 1; } impl Trait for Test { diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 56afc793330b5..a67323380169b 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -117,6 +117,31 @@ macro_rules! parameter_types { } } +/// Macro for easily creating a new implementation of both the `Get` and `Contains` traits. Use +/// exactly as with `parameter_types`, only the type must be `Ord`. +#[macro_export] +macro_rules! ord_parameter_types { + ( + $( #[ $attr:meta ] )* + $vis:vis const $name:ident: $type:ty = $value:expr; + $( $rest:tt )* + ) => ( + $( #[ $attr ] )* + $vis struct $name; + $crate::parameter_types!{IMPL $name , $type , $value} + $crate::ord_parameter_types!{IMPL $name , $type , $value} + $crate::ord_parameter_types!{ $( $rest )* } + ); + () => (); + (IMPL $name:ident , $type:ty , $value:expr) => { + impl $crate::traits::Contains<$type> for $name { + fn contains(t: &$type) -> bool { &$value == t } + fn sorted_members() -> $crate::sp_std::prelude::Vec<$type> { vec![$value] } + fn count() -> usize { 1 } + } + } +} + #[doc(inline)] pub use frame_support_procedural::{decl_storage, construct_runtime}; diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index e4092529a62ab..fb627f369db43 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -57,21 +57,13 @@ pub trait Contains { /// Return `true` if this "contains" the given value `t`. fn contains(t: &T) -> bool { Self::sorted_members().binary_search(t).is_ok() } - /// Get a vector of all members in the set, orderred. + /// Get a vector of all members in the set, ordered. fn sorted_members() -> Vec; /// Get the number of items in the set. fn count() -> usize { Self::sorted_members().len() } } -impl> Contains for G { - fn contains(t: &T) -> bool { &Self::get() == t } - fn sorted_members() -> Vec { - vec![Self::get()] - } - fn count() -> usize { 1 } -} - /// The account with the given id was killed. #[impl_trait_for_tuples::impl_for_tuples(30)] pub trait OnFreeBalanceZero { diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 76a12fac0c684..512325c0d733d 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -89,7 +89,7 @@ pub trait Trait: frame_system::Trait { /// Origin from which rejections must come. type RejectOrigin: EnsureOrigin; - /// Origin from which tippers must come. + /// Origin from which tippers must come. This must be sorted. type Tippers: Contains; /// The period for which a tip remains open after is has achieved threshold tippers. From 861b638f3888c4c3615f8159f261aa561ec2d90c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 3 Jan 2020 20:23:09 +0000 Subject: [PATCH 13/25] Fixes --- frame/scored-pool/src/mock.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frame/scored-pool/src/mock.rs b/frame/scored-pool/src/mock.rs index fe873da26a590..a63e03b57a809 100644 --- a/frame/scored-pool/src/mock.rs +++ b/frame/scored-pool/src/mock.rs @@ -19,7 +19,7 @@ use super::*; use std::cell::RefCell; -use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; +use frame_support::{impl_outer_origin, parameter_types, weights::Weight, ord_parameter_types}; use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried. @@ -41,9 +41,6 @@ parameter_types! { pub const CandidateDeposit: u64 = 25; pub const Period: u64 = 4; - pub const KickOrigin: u64 = 2; - pub const ScoreOrigin: u64 = 3; - pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: Weight = 1024; pub const MaximumBlockLength: u32 = 2 * 1024; @@ -53,6 +50,10 @@ parameter_types! { pub const TransferFee: u64 = 0; pub const CreationFee: u64 = 0; } +ord_parameter_types! { + pub const KickOrigin: u64 = 2; + pub const ScoreOrigin: u64 = 3; +} impl frame_system::Trait for Test { type Origin = Origin; From 3cea713cf6c7a95422a09e6910f3eaa721a02ee5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 4 Jan 2020 13:48:49 +0000 Subject: [PATCH 14/25] Docs. --- frame/treasury/src/lib.rs | 68 +++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 512325c0d733d..65541e8b22e39 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -25,12 +25,24 @@ //! ## Overview //! //! The Treasury Module itself provides the pot to store funds, and a means for stakeholders to -//! propose, approve, and deny expenditures. The chain will need to provide a method (e.g. +//! propose, approve, and deny expenditures. The chain will need to provide a method (e.g. //! inflation, fees) for collecting funds. //! //! By way of example, the Council could vote to fund the Treasury with a portion of the block //! reward and use the funds to pay developers. //! +//! ### Tipping +//! +//! A separate subsystem exists to allow for an agile "tipping" process, whereby a reward may be +//! given without first having a pre-determined stakeholder group come to consensus on how much +//! should be paid. +//! +//! A group of `Tippers` is determined through the config `Trait`. After half of these have declared +//! some amount that they believe a particular reported reason deserves, then a countfown period is +//! entered where any remaining members can declare their tip amounts also. After the close of the +//! countdown period, the median of all declared tips is paid to the reported beneficiary, along +//! with any finders fee, in case of a public (and bonded) original report. +//! //! ### Terminology //! //! - **Proposal:** A suggestion to allocate funds from the pot to a beneficiary. @@ -41,16 +53,34 @@ //! respectively. //! - **Pot:** Unspent funds accumulated by the treasury module. //! +//! Tipping protocol: +//! - **Tipping:** The process of gathering declarations of amounts to tip and taking the median +//! amount to be transferred from the treasury to a beneficiary account. +//! - **Tip Reason:** The reason for a tip; generally a URL which embodies or explains why a +//! particular individual (identified by an account ID) is worthy of a recognition by the +//! treasury. +//! - **Finder:** The original public reporter of some reason for tipping. +//! - **Finders Fee:** Some proportion of the tip amount that is paid to the reporter of the tip, +//! rather than the main beneficiary. +//! //! ## Interface //! //! ### Dispatchable Functions //! +//! General spending/proposal protocol: //! - `propose_spend` - Make a spending proposal and stake the required deposit. //! - `set_pot` - Set the spendable balance of funds. //! - `configure` - Configure the module's proposal requirements. //! - `reject_proposal` - Reject a proposal, slashing the deposit. //! - `approve_proposal` - Accept the proposal, returning the deposit. //! +//! Tipping protocol: +//! - `report_awesome` - Report something worthy of a tip and register for a finders fee. +//! - `retract_tip` - Retract a previous (finders fee registered) report. +//! - `tip_new` - Report an item worthy of a tip and declare a specific amount to tip. +//! - `tip` - Declare or redeclare an amount to tip for a particular reason. +//! - `close_tip` - Close and pay out a tip. +//! //! ## GenesisConfig //! //! The Treasury module depends on the [`GenesisConfig`](./struct.GenesisConfig.html). @@ -321,23 +351,23 @@ decl_module! { Self::deposit_event(RawEvent::NewTip(hash)); } - /// Report something `reason` that deserves a tip and claim any eventual the finder's fee. + /// Retract a prior tip-report from `report_awesome`, and cancel the process of tipping. /// - /// The dispatch origin for this call must be _Signed_. + /// If successful, the original deposit will be unreserved. /// - /// Payment: `TipReportDepositBase` will be reserved from the origin account, as well as - /// `TipReportDepositPerByte` for each byte in `reason`. + /// The dispatch origin for this call must be _Signed_ and the tip identified by `hash` + /// must have been reported by the signing account through `report_awesome` (and not + /// through `tip_new`). /// - /// - `reason`: The reason for, or the thing that deserves, the tip; generally this will be - /// a UTF-8-encoded URL. - /// - `who`: The account which should be credited for the tip. + /// - `hash`: The identity of the open tip for which a tip value is declared. This is formed + /// as the hash of the tuple of the original tip `reason` and the beneficiary account ID. /// - /// Emits `NewTip` if successful. + /// Emits `TipRetracted` if successful. /// /// # - /// - `O(R)` where `R` length of `reason`. + /// - `O(T)` /// - One balance operation. - /// - One storage mutation (codec `O(R)`). + /// - Two storage removals (one read, codec `O(T)`). /// - One event. /// # fn retract_tip(origin, hash: T::Hash) { @@ -366,8 +396,9 @@ decl_module! { /// Emits `NewTip` if successful. /// /// # - /// - `O(R)` where `R` length of `reason`. - /// - One storage mutation (codec `O(R)`). + /// - `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. /// # fn tip_new(origin, reason: Vec, who: T::AccountId, tip_value: BalanceOf) { @@ -399,8 +430,8 @@ decl_module! { /// has started. /// /// # - /// - `O(R)` where `R` is the original length of `reason`. - /// - One storage mutation (codec `O(R)`). + /// - `O(T)` + /// - One storage mutation (codec `O(T)`), one storage read `O(1)`. /// - Up to one event. /// # fn tip(origin, hash: T::Hash, tip_value: BalanceOf) { @@ -424,8 +455,8 @@ decl_module! { /// as the hash of the tuple of the original tip `reason` and the beneficiary account ID. /// /// # - /// - `O(R)` where `R` is the original length of `reason`. - /// - One storage retrieval and removal (codec `O(R)`). + /// - `O(T)` + /// - One storage retrieval (codec `O(T)`) and two removals. /// - Up to three balance operations. /// # fn close_tip(origin, hash: T::Hash) { @@ -532,6 +563,8 @@ impl Module { /// Given a mutable reference to an `OpenTip`, insert the tip into it and check whether it /// closes, if so, then deposit the relevant event and set closing accordingly. + /// + /// `O(T)` and one storage access. fn insert_tip_and_check_closing( tip: &mut OpenTip, T::BlockNumber, T::Hash>, tipper: T::AccountId, @@ -551,6 +584,7 @@ impl Module { } } + /// Remove any non-members of `Tippers` from a `tips` vectr. `O(T)`. fn retain_active_tips(tips: &mut Vec<(T::AccountId, BalanceOf)>) { let members = T::Tippers::sorted_members(); let mut members_iter = members.iter(); From 138bd6540657732ad18ce9eae20f80a38fac7692 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 4 Jan 2020 19:52:37 +0000 Subject: [PATCH 15/25] Docs. --- frame/treasury/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 65541e8b22e39..4f310468eb6d2 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -211,7 +211,8 @@ decl_storage! { pub Tips get(fn tips): map hasher(twox_64_concat) T::Hash => Option, T::BlockNumber, T::Hash>>; - /// Simple preimage lookup from the reason's hash to the original data. + /// Simple preimage lookup from the reason's hash to the original data. Again, has an + /// insecure enumerable hash since the key is guaranteed to be the result of a secure hash. pub Reasons get(fn reasons): map hasher(twox_64_concat) T::Hash => Option>; } add_extra_genesis { From 6816d0da1b838d78d7e701bba258f5e2ebe35f8c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 4 Jan 2020 19:57:42 +0000 Subject: [PATCH 16/25] Typo --- frame/support/src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index fb627f369db43..2ee248901d971 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -97,7 +97,7 @@ impl FindAuthor for () { /// A trait for verifying the seal of a header and returning the author. pub trait VerifySeal { - /// Verify a header and r;-89-9;~eturn the author, if any. + /// Verify a header and return the author, if any. fn verify_seal(header: &Header) -> Result, &'static str>; } From fa95eeba676b3dfd91c4ff1b410c45b2eaa59604 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 4 Jan 2020 19:58:53 +0000 Subject: [PATCH 17/25] Whitespace --- frame/treasury/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 4f310468eb6d2..f925829dbf4f3 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -25,7 +25,7 @@ //! ## Overview //! //! The Treasury Module itself provides the pot to store funds, and a means for stakeholders to -//! propose, approve, and deny expenditures. The chain will need to provide a method (e.g. +//! propose, approve, and deny expenditures. The chain will need to provide a method (e.g. //! inflation, fees) for collecting funds. //! //! By way of example, the Council could vote to fund the Treasury with a portion of the block From 4c57a234780c066ed0bd5b6ffa26971c500e7fe5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 4 Jan 2020 19:59:34 +0000 Subject: [PATCH 18/25] Docs --- frame/treasury/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index f925829dbf4f3..e0319451a7f99 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -119,7 +119,7 @@ pub trait Trait: frame_system::Trait { /// Origin from which rejections must come. type RejectOrigin: EnsureOrigin; - /// Origin from which tippers must come. This must be sorted. + /// Origin from which tippers must come. type Tippers: Contains; /// The period for which a tip remains open after is has achieved threshold tippers. From 905b851cfa211416a828045b1d06ba7f8361c7b8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 4 Jan 2020 20:02:44 +0000 Subject: [PATCH 19/25] Typo --- frame/treasury/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index e0319451a7f99..0f49b6d847d6a 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -163,7 +163,7 @@ pub type ProposalIndex = u32; pub struct Proposal { /// The account proposing it. proposer: AccountId, - /// The (total) amount that should be pad if the proposal is accepted. + /// The (total) amount that should be paid if the proposal is accepted. value: Balance, /// The account to whom the payment should be made if the proposal is accepted. beneficiary: AccountId, From e912d4cc04fd1487dcc1992fd9b8769a4920ec3d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 5 Jan 2020 18:26:25 +0000 Subject: [PATCH 20/25] Formatting --- frame/treasury/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 0f49b6d847d6a..885b050f55fd9 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -373,10 +373,10 @@ decl_module! { /// # fn retract_tip(origin, hash: T::Hash) { let who = ensure_signed(origin)?; - let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; let (finder, deposit) = tip.finder.ok_or(Error::::NotFinder)?; ensure!(finder == who, Error::::NotFinder); + Reasons::::remove(&tip.reason); Tips::::remove(&hash); let _ = T::Currency::unreserve(&who, deposit); From 7d8e24976546ee03a6c1370e835629d7a3bd7688 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 9 Jan 2020 13:44:32 +0100 Subject: [PATCH 21/25] Update frame/treasury/src/lib.rs Co-Authored-By: Shawn Tabrizi --- frame/treasury/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 885b050f55fd9..e5009c7bc6feb 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -249,7 +249,7 @@ decl_event!( Deposit(Balance), /// A new tip suggestion has been opened. NewTip(Hash), - /// A tip suggestion has reached threshold and . + /// A tip suggestion has reached threshold and is closing. TipClosing(Hash), /// A new tip suggestion has been opened. TipClosed(Hash, AccountId, Balance), From 834a7ba7c1b10836ba674404a70df912deab241d Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 9 Jan 2020 13:45:05 +0100 Subject: [PATCH 22/25] Update frame/treasury/src/lib.rs Co-Authored-By: Shawn Tabrizi --- frame/treasury/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index e5009c7bc6feb..2e65743d770fc 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -180,7 +180,7 @@ pub struct OpenTip< BlockNumber: Parameter, Hash: Parameter, > { - /// The reason for the tip. Should be a human-readable UTF-8 encoded string. A URL would be + /// The hash of the reason for the tip. The reason should be a human-readable UTF-8 encoded string. A URL would be /// sensible. reason: Hash, /// The account to be tipped. From 73357e2b9969a721bf5ef72b5fea0ae600316939 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 9 Jan 2020 13:49:40 +0100 Subject: [PATCH 23/25] Update frame/treasury/src/lib.rs Co-Authored-By: Shawn Tabrizi --- frame/treasury/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 2e65743d770fc..d729c64232bf0 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -125,7 +125,7 @@ pub trait Trait: frame_system::Trait { /// The period for which a tip remains open after is has achieved threshold tippers. type TipCountdown: Get; - /// The amount of the final tip which goes to the original reporter of the tip. + /// The percent of the final tip which goes to the original reporter of the tip. type TipFindersFee: Get; /// The amount held on deposit for placing a tip report. From b95e6d5f1a08132f43bdf9f2c505ee090b19e608 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 9 Jan 2020 13:53:17 +0100 Subject: [PATCH 24/25] Apply suggestions from code review Co-Authored-By: Shawn Tabrizi --- frame/treasury/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index d729c64232bf0..297d332179939 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -251,9 +251,9 @@ decl_event!( NewTip(Hash), /// A tip suggestion has reached threshold and is closing. TipClosing(Hash), - /// A new tip suggestion has been opened. + /// A tip suggestion has been closed. TipClosed(Hash, AccountId, Balance), - /// A new tip suggestion has been opened. + /// A tip suggestion has been retracted. TipRetracted(Hash), } ); @@ -265,7 +265,7 @@ decl_error! { InsufficientProposersBalance, /// No proposal at that index. InvalidProposalIndex, - /// The reason give is just too big. + /// The reason given is just too big. ReasonTooBig, /// The tip was already found/started. AlreadyKnown, @@ -833,7 +833,6 @@ mod tests { #[test] fn tip_new_cannot_be_used_twice() { 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::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); assert_noop!( @@ -846,7 +845,6 @@ mod tests { #[test] fn report_awesome_and_tip_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::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); assert_eq!(Balances::reserved_balance(&0), 12); @@ -874,7 +872,6 @@ mod tests { #[test] fn report_awesome_from_beneficiary_and_tip_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::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 0)); assert_eq!(Balances::reserved_balance(&0), 12); @@ -893,7 +890,6 @@ mod tests { #[test] fn close_tip_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_eq!(Treasury::pot(), 100); @@ -917,7 +913,6 @@ mod tests { #[test] fn retract_tip_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::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); let h = tip_hash(); @@ -934,7 +929,6 @@ mod tests { #[test] fn tip_median_calculation_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::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); let h = tip_hash(); @@ -949,7 +943,6 @@ mod tests { #[test] fn tip_changing_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::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); let h = tip_hash(); From c0f8d64da74204072bd669fa06e368972049df21 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 9 Jan 2020 13:55:59 +0100 Subject: [PATCH 25/25] Add provisional weights. --- frame/treasury/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 297d332179939..b08d423cc860c 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -330,6 +330,7 @@ decl_module! { /// - One storage mutation (codec `O(R)`). /// - One event. /// # + #[weight = SimpleDispatchInfo::FixedNormal(100_000)] fn report_awesome(origin, reason: Vec, who: T::AccountId) { let finder = ensure_signed(origin)?; @@ -371,6 +372,7 @@ decl_module! { /// - Two storage removals (one read, codec `O(T)`). /// - One event. /// # + #[weight = SimpleDispatchInfo::FixedNormal(50_000)] fn retract_tip(origin, hash: T::Hash) { let who = ensure_signed(origin)?; let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; @@ -402,6 +404,7 @@ decl_module! { /// - Two storage insertions (codecs `O(R)`, `O(T)`), one read `O(1)`. /// - One event. /// # + #[weight = SimpleDispatchInfo::FixedNormal(150_000)] fn tip_new(origin, reason: Vec, who: T::AccountId, tip_value: BalanceOf) { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); @@ -435,6 +438,7 @@ decl_module! { /// - One storage mutation (codec `O(T)`), one storage read `O(1)`. /// - Up to one event. /// # + #[weight = SimpleDispatchInfo::FixedNormal(50_000)] fn tip(origin, hash: T::Hash, tip_value: BalanceOf) { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); @@ -460,6 +464,7 @@ decl_module! { /// - One storage retrieval (codec `O(T)`) and two removals. /// - Up to three balance operations. /// # + #[weight = SimpleDispatchInfo::FixedNormal(50_000)] fn close_tip(origin, hash: T::Hash) { ensure_signed(origin)?;