diff --git a/frame/tips/src/benchmarking.rs b/frame/tips/src/benchmarking.rs index e6a0284d82307..ebb5e77e8b58d 100644 --- a/frame/tips/src/benchmarking.rs +++ b/frame/tips/src/benchmarking.rs @@ -22,10 +22,18 @@ use super::*; use frame_system::RawOrigin; -use frame_benchmarking::{benchmarks, account, whitelisted_caller, impl_benchmark_test_suite}; +use frame_benchmarking::{ + benchmarks, + account, + whitelisted_caller, + impl_benchmark_test_suite, +}; +use frame_support::{ + ensure, +}; use sp_runtime::{traits::{Saturating}}; -use crate::Module as TipsMod; +use crate::Pallet as TipsMod; const SEED: u32 = 0; @@ -85,7 +93,8 @@ fn setup_pot_account() { let _ = T::Currency::make_free_balance_be(&pot_account, value); } -const MAX_BYTES: u32 = 16384; +// const MAX_BYTES: u32 = 16384; +const MAX_BYTES: u32 = 2; const MAX_TIPPERS: u32 = 100; benchmarks! { @@ -191,6 +200,7 @@ benchmarks! { let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); ensure!(Tips::::contains_key(hash), "tip does not exist"); }: _(RawOrigin::Root, hash) + } impl_benchmark_test_suite!( diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index 6d85df33f10c9..ec8ba344e6526 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -54,56 +54,37 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] mod tests; mod benchmarking; pub mod weights; use sp_std::prelude::*; -use frame_support::{decl_module, decl_storage, decl_event, ensure, decl_error, Parameter}; -use frame_support::traits::{ - Currency, Get, ExistenceRequirement::{KeepAlive}, - ReservableCurrency -}; +use frame_support::{ + Parameter, + storage::migration::{move_storage_from_pallet}, + traits::{ + Currency, Get, ExistenceRequirement::{KeepAlive}, + ReservableCurrency, Contains, ContainsLengthBound, + OnUnbalanced, PalletInfo, + }, +}; +use frame_system::{self as system}; use sp_runtime::{ Percent, RuntimeDebug, traits::{ Zero, AccountIdConversion, Hash, BadOrigin }}; -use frame_support::traits::{Contains, ContainsLengthBound, OnUnbalanced, EnsureOrigin}; +#[cfg(feature = "std")] +use frame_support::traits::GenesisBuild; + use codec::{Encode, Decode}; -use frame_system::{self as system, ensure_signed}; pub use weights::WeightInfo; +pub use pallet::*; + pub type BalanceOf = pallet_treasury::BalanceOf; pub type NegativeImbalanceOf = pallet_treasury::NegativeImbalanceOf; -pub trait Config: frame_system::Config + pallet_treasury::Config { - /// Maximum acceptable reason length. - type MaximumReasonLength: Get; - - /// The amount held on deposit per byte within the tip report reason or bounty description. - type DataDepositPerByte: Get>; - - /// Origin from which tippers must come. - /// - /// `ContainsLengthBound::max_len` must be cost free (i.e. no storage read or heavy operation). - type Tippers: Contains + ContainsLengthBound; - - /// The period for which a tip remains open after is has achieved threshold tippers. - type TipCountdown: Get; - - /// 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. - type TipReportDepositBase: Get>; - - /// The overarching event type. - type Event: From> + Into<::Event>; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; -} - /// 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)] @@ -131,90 +112,60 @@ pub struct OpenTip< finders_fee: bool, } -// Note :: For backward compatability reasons, -// pallet-tips uses Treasury for storage. -// This is temporary solution, soon will get replaced with -// Own storage identifier. -decl_storage! { - trait Store for Module as Treasury { - - /// TipsMap 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, T::Hash>>; - - /// 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(identity) T::Hash => Option>; +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use super::*; - } -} + #[pallet::config] + pub trait Config: frame_system::Config + pallet_treasury::Config { + /// Maximum acceptable reason length. + type MaximumReasonLength: Get; -decl_event!( - pub enum Event - where - Balance = BalanceOf, - ::AccountId, - ::Hash, - { - /// A new tip suggestion has been opened. \[tip_hash\] - NewTip(Hash), - /// A tip suggestion has reached threshold and is closing. \[tip_hash\] - TipClosing(Hash), - /// A tip suggestion has been closed. \[tip_hash, who, payout\] - TipClosed(Hash, AccountId, Balance), - /// A tip suggestion has been retracted. \[tip_hash\] - TipRetracted(Hash), - /// A tip suggestion has been slashed. \[tip_hash, finder, deposit\] - TipSlashed(Hash, AccountId, Balance), - } -); + /// The overarching event type. + type Event: From> + IsType<::Event>; -decl_error! { - /// Error for the tips module. - pub enum Error for Module { - /// The reason given 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, - /// 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, - } -} + /// The amount held on deposit per byte within the tip report reason or bounty description. + type DataDepositPerByte: Get>; -decl_module! { - pub struct Module - for enum Call - where origin: T::Origin - { + /// Origin from which tippers must come. + /// + /// `ContainsLengthBound::max_len` must be cost free (i.e. no storage read or heavy operation). + type Tippers: Contains + ContainsLengthBound; /// The period for which a tip remains open after is has achieved threshold tippers. - const TipCountdown: T::BlockNumber = T::TipCountdown::get(); + type TipCountdown: Get; - /// The amount of the final tip which goes to the original reporter of the tip. - const TipFindersFee: Percent = T::TipFindersFee::get(); + /// 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. - const TipReportDepositBase: BalanceOf = T::TipReportDepositBase::get(); + type TipReportDepositBase: Get>; - /// The amount held on deposit per byte within the tip report reason. - const DataDepositPerByte: BalanceOf = T::DataDepositPerByte::get(); + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } - /// Maximum acceptable reason length. - const MaximumReasonLength: u32 = T::MaximumReasonLength::get(); + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); - type Error = Error; + #[pallet::hooks] + impl Hooks> for Pallet { - fn deposit_event() = default; + fn on_runtime_upgrade() -> frame_support::weights::Weight { + if !UpgradedPalletPrefix::::get() { + UpgradedPalletPrefix::::put(true); + migrations::migrate_to_new_pallet_prefix::() + } else { + 0 + } + } + } + #[pallet::call] + impl Pallet { /// Report something `reason` that deserves a tip and claim any eventual the finder's fee. /// /// The dispatch origin for this call must be _Signed_. @@ -227,29 +178,36 @@ decl_module! { /// - `who`: The account which should be credited for the tip. /// /// Emits `NewTip` if successful. - /// - /// # - /// - Complexity: `O(R)` where `R` length of `reason`. - /// - encoding and hashing of 'reason' - /// - DbReads: `Reasons`, `Tips` - /// - DbWrites: `Reasons`, `Tips` - /// # - #[weight = ::WeightInfo::report_awesome(reason.len() as u32)] - fn report_awesome(origin, reason: Vec, who: T::AccountId) { + #[pallet::weight(::WeightInfo::report_awesome(reason.len() as u32))] + pub fn report_awesome( + origin: OriginFor, + reason: Vec, + who: T::AccountId + ) -> DispatchResultWithPostInfo { let finder = ensure_signed(origin)?; - ensure!(reason.len() <= T::MaximumReasonLength::get() as usize, Error::::ReasonTooBig); + ensure!( + reason.len() <= T::MaximumReasonLength::get() as usize, + Error::::ReasonTooBig + ); let reason_hash = T::Hashing::hash(&reason[..]); - ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); + ensure!( + !>::contains_key(&reason_hash), + Error::::AlreadyKnown + ); + let hash = T::Hashing::hash_of(&(&reason_hash, &who)); - ensure!(!Tips::::contains_key(&hash), Error::::AlreadyKnown); + ensure!( + !>::contains_key(&hash), + Error::::AlreadyKnown + ); let deposit = T::TipReportDepositBase::get() + T::DataDepositPerByte::get() * (reason.len() as u32).into(); T::Currency::reserve(&finder, deposit)?; - Reasons::::insert(&reason_hash, &reason); + >::insert(&reason_hash, reason); let tip = OpenTip { reason: reason_hash, who, @@ -259,8 +217,9 @@ decl_module! { tips: vec![], finders_fee: true }; - Tips::::insert(&hash, tip); - Self::deposit_event(RawEvent::NewTip(hash)); + >::insert(&hash, tip); + Self::deposit_event(Event::NewTip(hash)); + Ok(().into()) } /// Retract a prior tip-report from `report_awesome`, and cancel the process of tipping. @@ -275,26 +234,23 @@ decl_module! { /// as the hash of the tuple of the original tip `reason` and the beneficiary account ID. /// /// Emits `TipRetracted` if successful. - /// - /// # - /// - Complexity: `O(1)` - /// - Depends on the length of `T::Hash` which is fixed. - /// - DbReads: `Tips`, `origin account` - /// - DbWrites: `Reasons`, `Tips`, `origin account` - /// # - #[weight = ::WeightInfo::retract_tip()] - fn retract_tip(origin, hash: T::Hash) { + #[pallet::weight(::WeightInfo::retract_tip())] + pub fn retract_tip( + origin: OriginFor, + hash: T::Hash + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; + let tip = >::get(&hash).ok_or(Error::::UnknownTip)?; ensure!(tip.finder == who, Error::::NotFinder); - Reasons::::remove(&tip.reason); - Tips::::remove(&hash); + >::remove(&tip.reason); + >::remove(&hash); if !tip.deposit.is_zero() { let err_amount = T::Currency::unreserve(&who, tip.deposit); debug_assert!(err_amount.is_zero()); } - Self::deposit_event(RawEvent::TipRetracted(hash)); + Self::deposit_event(Event::TipRetracted(hash)); + Ok(().into()) } /// Give a tip for something new; no finder's fee will be taken. @@ -309,26 +265,25 @@ decl_module! { /// value of active tippers will be given to the `who`. /// /// Emits `NewTip` if successful. - /// - /// # - /// - 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 = ::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32)] - fn tip_new(origin, reason: Vec, who: T::AccountId, #[compact] tip_value: BalanceOf) { + #[pallet::weight(::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))] + pub fn tip_new( + origin: OriginFor, + reason: Vec, + who: T::AccountId, + #[pallet::compact] tip_value: BalanceOf, + ) -> DispatchResultWithPostInfo { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); + let reason_hash = T::Hashing::hash(&reason[..]); - ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); + ensure!( + !Reasons::::contains_key(&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())); + >::insert(&reason_hash, reason); + Self::deposit_event(Event::NewTip(hash.clone())); let tips = vec![(tipper.clone(), tip_value)]; let tip = OpenTip { reason: reason_hash, @@ -339,7 +294,8 @@ decl_module! { tips, finders_fee: false, }; - Tips::::insert(&hash, tip); + >::insert(&hash, tip); + Ok(().into()) } /// Declare a tip value for an already-open tip. @@ -355,28 +311,21 @@ decl_module! { /// /// Emits `TipClosing` if the threshold of tippers has been reached and the countdown period /// has started. - /// - /// # - /// - 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 = ::WeightInfo::tip(T::Tippers::max_len() as u32)] - fn tip(origin, hash: T::Hash, #[compact] tip_value: BalanceOf) { + #[pallet::weight(::WeightInfo::tip(T::Tippers::max_len() as u32))] + pub fn tip( + origin: OriginFor, + hash: T::Hash, + #[pallet::compact] tip_value: BalanceOf + ) -> DispatchResultWithPostInfo { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); - let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + let mut tip = >::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())); + Self::deposit_event(Event::TipClosing(hash.clone())); } - Tips::::insert(&hash, tip); + >::insert(&hash, tip); + Ok(().into()) } /// Close and payout a tip. @@ -387,26 +336,25 @@ decl_module! { /// /// - `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. - /// - /// # - /// - 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 = ::WeightInfo::close_tip(T::Tippers::max_len() as u32)] - fn close_tip(origin, hash: T::Hash) { + #[pallet::weight(::WeightInfo::close_tip(T::Tippers::max_len() as u32))] + pub fn close_tip( + origin: OriginFor, + hash: T::Hash + ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; - let tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + let tip = >::get(hash).ok_or(Error::::UnknownTip)?; let n = tip.closes.as_ref().ok_or(Error::::StillOpen)?; - ensure!(system::Pallet::::block_number() >= *n, Error::::Premature); + ensure!( + // system::Module::::block_number() >= *n, + system::Pallet::::block_number() >= *n, + Error::::Premature + ); // closed. - Reasons::::remove(&tip.reason); - Tips::::remove(hash); + >::remove(&tip.reason); + >::remove(hash); Self::payout_tip(hash, tip); + Ok(().into()) } /// Remove and slash an already-open tip. @@ -416,30 +364,147 @@ decl_module! { /// As a result, the finder is slashed and the deposits are lost. /// /// Emits `TipSlashed` if successful. - /// - /// # - /// `T` is charged as upper bound given by `ContainsLengthBound`. - /// The actual cost depends on the implementation of `T::Tippers`. - /// # - #[weight = ::WeightInfo::slash_tip(T::Tippers::max_len() as u32)] - fn slash_tip(origin, hash: T::Hash) { + #[pallet::weight(::WeightInfo::slash_tip(T::Tippers::max_len() as u32))] + pub fn slash_tip( + origin: OriginFor, + hash: T::Hash + ) -> DispatchResultWithPostInfo { T::RejectOrigin::ensure_origin(origin)?; - let tip = Tips::::take(hash).ok_or(Error::::UnknownTip)?; + let tip = >::take(hash).ok_or(Error::::UnknownTip)?; if !tip.deposit.is_zero() { let imbalance = T::Currency::slash_reserved(&tip.finder, tip.deposit).0; T::OnSlash::on_unbalanced(imbalance); } - Reasons::::remove(&tip.reason); - Self::deposit_event(RawEvent::TipSlashed(hash, tip.finder, tip.deposit)); + >::remove(&tip.reason); + Self::deposit_event(Event::TipSlashed(hash, tip.finder, tip.deposit)); + Ok(().into()) } + + } + + #[pallet::error] + pub enum Error { + /// The reason given 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, + /// 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, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", BalanceOf = "Balance", T::Hash = "Hash")] + pub enum Event { + /// A new tip suggestion has been opened. \[tip_hash\] + NewTip(T::Hash), + /// A tip suggestion has reached threshold and is closing. \[tip_hash\] + TipClosing(T::Hash), + /// A tip suggestion has been closed. \[tip_hash, who, payout\] + TipClosed(T::Hash, T::AccountId, BalanceOf), + /// A tip suggestion has been retracted. \[tip_hash\] + TipRetracted(T::Hash), + /// A tip suggestion has been slashed. \[tip_hash, finder, deposit\] + TipSlashed(T::Hash, T::AccountId, BalanceOf), + } + + #[pallet::storage] + #[pallet::getter(fn tips)] + pub type Tips = StorageMap< + _, + Twox64Concat, + T::Hash, + OpenTip, T::BlockNumber, T::Hash>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn reasons)] + pub type Reasons = StorageMap< + _, + Twox64Concat, + T::Hash, + Vec, + OptionQuery, + >; + + /// True if we have upgraded so that AccountInfo contains three types of `RefCount`. False + /// (default) if not. + #[pallet::storage] + pub(super) type UpgradedPalletPrefix = StorageValue<_, bool, ValueQuery>; + + // TODO :: Have to recheck + // Since each pallet is has own storage + // Tips is expected to have own storage & not + // Share with Treasury. + #[pallet::genesis_config] + pub struct GenesisConfig{ + pub phantom: sp_std::marker::PhantomData, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + phantom: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + >::put(true); + } + } +} + +mod migrations { + use super::*; + + /// Migrate from dual `u32` reference counting to triple `u32` reference counting. + pub fn migrate_to_new_pallet_prefix() -> frame_support::weights::Weight { + + let new_name = T::PalletInfo::name::>() + .expect("Fatal Error Invalid PalletInfo name") + .as_bytes(); + + move_storage_from_pallet(b"Tips", b"Treasury", new_name); + move_storage_from_pallet(b"Reasons", b"Treasury", new_name); + + T::BlockWeights::get().max_block } } -impl Module { - // Add public immutables and private mutables. +#[cfg(feature = "std")] +impl GenesisConfig { + /// Direct implementation of `GenesisBuild::build_storage`. + /// + /// Kept in order not to break dependency. + pub fn build_storage(&self) -> Result { + >::build_storage(self) + } + + /// Direct implementation of `GenesisBuild::assimilate_storage`. + /// + /// Kept in order not to break dependency. + pub fn assimilate_storage( + &self, + storage: &mut sp_runtime::Storage + ) -> Result<(), String> { + >::assimilate_storage(self, storage) + } +} +impl Pallet { /// The account ID of the treasury pot. /// /// This actually does computation. If you need to keep using it, then make sure you cache the @@ -502,7 +567,8 @@ impl Module { tips.sort_by_key(|i| i.1); let treasury = Self::account_id(); - let max_payout = pallet_treasury::Module::::pot(); + // let max_payout = pallet_treasury::Module::::pot(); + let max_payout = pallet_treasury::Pallet::::pot(); let mut payout = tips[tips.len() / 2].1.min(max_payout); if !tip.deposit.is_zero() { @@ -523,7 +589,7 @@ impl Module { // same as above: best-effort only. let res = T::Currency::transfer(&treasury, &tip.who, payout, KeepAlive); debug_assert!(res.is_ok()); - Self::deposit_event(RawEvent::TipClosed(hash, tip.who, payout)); + Self::deposit_event(Event::TipClosed(hash, tip.who, payout)); } pub fn migrate_retract_tip_for_tip_new() { @@ -556,9 +622,8 @@ impl Module { T::Hash, OldOpenTip, T::BlockNumber, T::Hash>, Twox64Concat, - >::new(b"Treasury", b"Tips").drain() + >::new(b"Tips", b"Tips").drain() { - let (finder, deposit, finders_fee) = match old_tip.finder { Some((finder, deposit)) => { (finder, deposit, true) diff --git a/frame/tips/src/tests.rs b/frame/tips/src/tests.rs index ef30962fc846f..5ee498c17ca3e 100644 --- a/frame/tips/src/tests.rs +++ b/frame/tips/src/tests.rs @@ -19,8 +19,8 @@ #![cfg(test)] -use crate as tips; use super::*; +use crate as pallet_tips; use std::cell::RefCell; use frame_support::{assert_noop, assert_ok, parameter_types, weights::Weight, traits::Contains}; use sp_runtime::Permill; @@ -30,23 +30,6 @@ use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup, BadOrigin}, }; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, - TipsModTestInst: tips::{Pallet, Call, Storage, Event}, - } -); - parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: Weight = 1024; @@ -154,23 +137,43 @@ impl Config for Test { type WeightInfo = (); } +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Tips: pallet_tips::{Pallet, Call, Storage, Event}, + } +); + pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig::{ - // Total issuance will be 200 with treasury account initialized at ED. + + pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 98), (2, 1)], }.assimilate_storage(&mut t).unwrap(); - pallet_treasury::GenesisConfig::default().assimilate_storage::(&mut t).unwrap(); - t.into() + + pallet_treasury::GenesisConfig::::default() + .assimilate_storage(&mut t).unwrap(); + + pallet_tips::GenesisConfig::::default() + .assimilate_storage(&mut t).unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + + ext } -fn last_event() -> RawEvent { - System::events().into_iter().map(|r| r.event) - .filter_map(|e| { - if let Event::tips(inner) = e { Some(inner) } else { None } - }) - .last() - .unwrap() +fn last_event() -> Event { + system::Pallet::::events().pop().expect("Event expected").event } #[test] @@ -189,9 +192,9 @@ fn tip_hash() -> H256 { fn tip_new_cannot_be_used_twice() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(TipsModTestInst::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); assert_noop!( - TipsModTestInst::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10), + Tips::tip_new(Origin::signed(11), b"awesome.dot".to_vec(), 3, 10), Error::::AlreadyKnown ); }); @@ -201,23 +204,23 @@ fn tip_new_cannot_be_used_twice() { fn report_awesome_and_tip_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(TipsModTestInst::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + assert_ok!(Tips::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!( - TipsModTestInst::report_awesome(Origin::signed(1), b"awesome.dot".to_vec(), 3), + Tips::report_awesome(Origin::signed(1), b"awesome.dot".to_vec(), 3), Error::::AlreadyKnown ); let h = tip_hash(); - assert_ok!(TipsModTestInst::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(TipsModTestInst::tip(Origin::signed(9), h.clone(), 10), BadOrigin); + assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Tips::tip(Origin::signed(9), h.clone(), 10), BadOrigin); System::set_block_number(2); - assert_ok!(TipsModTestInst::close_tip(Origin::signed(100), h.into())); + assert_ok!(Tips::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); @@ -228,15 +231,15 @@ fn report_awesome_and_tip_works() { fn report_awesome_from_beneficiary_and_tip_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(TipsModTestInst::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 0)); + assert_ok!(Tips::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(&(BlakeTwo256::hash(b"awesome.dot"), 0u128)); - assert_ok!(TipsModTestInst::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); System::set_block_number(2); - assert_ok!(TipsModTestInst::close_tip(Origin::signed(100), h.into())); + assert_ok!(Tips::close_tip(Origin::signed(100), h.into())); assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 110); }); @@ -250,30 +253,39 @@ fn close_tip_works() { Balances::make_free_balance_be(&Treasury::account_id(), 101); assert_eq!(Treasury::pot(), 100); - assert_ok!(TipsModTestInst::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); let h = tip_hash(); - assert_eq!(last_event(), RawEvent::NewTip(h)); + assert_eq!( + last_event(), + Event::pallet_tips(crate::Event::NewTip(h)), + ); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); - assert_noop!(TipsModTestInst::close_tip(Origin::signed(0), h.into()), Error::::StillOpen); + assert_noop!(Tips::close_tip(Origin::signed(0), h.into()), Error::::StillOpen); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); - assert_eq!(last_event(), RawEvent::TipClosing(h)); + assert_eq!( + last_event(), + Event::pallet_tips(crate::Event::TipClosing(h)), + ); - assert_noop!(TipsModTestInst::close_tip(Origin::signed(0), h.into()), Error::::Premature); + assert_noop!(Tips::close_tip(Origin::signed(0), h.into()), Error::::Premature); System::set_block_number(2); - assert_noop!(TipsModTestInst::close_tip(Origin::none(), h.into()), BadOrigin); - assert_ok!(TipsModTestInst::close_tip(Origin::signed(0), h.into())); + assert_noop!(Tips::close_tip(Origin::none(), h.into()), BadOrigin); + assert_ok!(Tips::close_tip(Origin::signed(0), h.into())); assert_eq!(Balances::free_balance(3), 10); - assert_eq!(last_event(), RawEvent::TipClosed(h, 3, 10)); + assert_eq!( + last_event(), + Event::pallet_tips(crate::Event::TipClosed(h, 3, 10)), + ); - assert_noop!(TipsModTestInst::close_tip(Origin::signed(100), h.into()), Error::::UnknownTip); + assert_noop!(Tips::close_tip(Origin::signed(100), h.into()), Error::::UnknownTip); }); } @@ -287,23 +299,29 @@ fn slash_tip_works() { assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::free_balance(0), 100); - assert_ok!(TipsModTestInst::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + assert_ok!(Tips::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); let h = tip_hash(); - assert_eq!(last_event(), RawEvent::NewTip(h)); + assert_eq!( + last_event(), + Event::pallet_tips(crate::Event::NewTip(h)), + ); // can't remove from any origin assert_noop!( - TipsModTestInst::slash_tip(Origin::signed(0), h.clone()), + Tips::slash_tip(Origin::signed(0), h.clone()), BadOrigin, ); // can remove from root. - assert_ok!(TipsModTestInst::slash_tip(Origin::root(), h.clone())); - assert_eq!(last_event(), RawEvent::TipSlashed(h, 0, 12)); + assert_ok!(Tips::slash_tip(Origin::root(), h.clone())); + assert_eq!( + last_event(), + Event::pallet_tips(crate::Event::TipSlashed(h, 0, 12)), + ); // tipper slashed assert_eq!(Balances::reserved_balance(0), 0); @@ -316,26 +334,26 @@ fn retract_tip_works() { new_test_ext().execute_with(|| { // with report awesome Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(TipsModTestInst::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + assert_ok!(Tips::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); let h = tip_hash(); - assert_ok!(TipsModTestInst::tip(Origin::signed(10), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(TipsModTestInst::retract_tip(Origin::signed(10), h.clone()), Error::::NotFinder); - assert_ok!(TipsModTestInst::retract_tip(Origin::signed(0), h.clone())); + assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Tips::retract_tip(Origin::signed(10), h.clone()), Error::::NotFinder); + assert_ok!(Tips::retract_tip(Origin::signed(0), h.clone())); System::set_block_number(2); - assert_noop!(TipsModTestInst::close_tip(Origin::signed(0), h.into()), Error::::UnknownTip); + assert_noop!(Tips::close_tip(Origin::signed(0), h.into()), Error::::UnknownTip); // with tip new Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(TipsModTestInst::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); + assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10)); let h = tip_hash(); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 10)); - assert_noop!(TipsModTestInst::retract_tip(Origin::signed(0), h.clone()), Error::::NotFinder); - assert_ok!(TipsModTestInst::retract_tip(Origin::signed(10), h.clone())); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Tips::retract_tip(Origin::signed(0), h.clone()), Error::::NotFinder); + assert_ok!(Tips::retract_tip(Origin::signed(10), h.clone())); System::set_block_number(2); - assert_noop!(TipsModTestInst::close_tip(Origin::signed(10), h.into()), Error::::UnknownTip); + assert_noop!(Tips::close_tip(Origin::signed(10), h.into()), Error::::UnknownTip); }); } @@ -343,12 +361,12 @@ fn retract_tip_works() { fn tip_median_calculation_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(TipsModTestInst::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); + assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 0)); let h = tip_hash(); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 10)); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 1000000)); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 1000000)); System::set_block_number(2); - assert_ok!(TipsModTestInst::close_tip(Origin::signed(0), h.into())); + assert_ok!(Tips::close_tip(Origin::signed(0), h.into())); assert_eq!(Balances::free_balance(3), 10); }); } @@ -357,17 +375,17 @@ fn tip_median_calculation_works() { fn tip_changing_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&Treasury::account_id(), 101); - assert_ok!(TipsModTestInst::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); + assert_ok!(Tips::tip_new(Origin::signed(10), b"awesome.dot".to_vec(), 3, 10000)); let h = tip_hash(); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 10000)); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 10000)); - assert_ok!(TipsModTestInst::tip(Origin::signed(13), h.clone(), 0)); - assert_ok!(TipsModTestInst::tip(Origin::signed(14), h.clone(), 0)); - assert_ok!(TipsModTestInst::tip(Origin::signed(12), h.clone(), 1000)); - assert_ok!(TipsModTestInst::tip(Origin::signed(11), h.clone(), 100)); - assert_ok!(TipsModTestInst::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 10000)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 10000)); + assert_ok!(Tips::tip(Origin::signed(13), h.clone(), 0)); + assert_ok!(Tips::tip(Origin::signed(14), h.clone(), 0)); + assert_ok!(Tips::tip(Origin::signed(12), h.clone(), 1000)); + assert_ok!(Tips::tip(Origin::signed(11), h.clone(), 100)); + assert_ok!(Tips::tip(Origin::signed(10), h.clone(), 10)); System::set_block_number(2); - assert_ok!(TipsModTestInst::close_tip(Origin::signed(0), h.into())); + assert_ok!(Tips::close_tip(Origin::signed(0), h.into())); assert_eq!(Balances::free_balance(3), 10); }); } @@ -423,11 +441,11 @@ fn test_last_reward_migration() { let data = vec![ ( - Tips::::hashed_key_for(hash1), + pallet_tips::Tips::::hashed_key_for(hash1), old_tip_finder.encode().to_vec() ), ( - Tips::::hashed_key_for(hash2), + pallet_tips::Tips::::hashed_key_for(hash2), old_tip_no_finder.encode().to_vec() ), ]; @@ -436,11 +454,11 @@ fn test_last_reward_migration() { sp_io::TestExternalities::new(s).execute_with(|| { - TipsModTestInst::migrate_retract_tip_for_tip_new(); + Tips::migrate_retract_tip_for_tip_new(); // Test w/ finder assert_eq!( - Tips::::get(hash1), + pallet_tips::Tips::::get(hash1), Some(OpenTip { reason: reason1, who: 10, @@ -454,7 +472,7 @@ fn test_last_reward_migration() { // Test w/o finder assert_eq!( - Tips::::get(hash2), + pallet_tips::Tips::::get(hash2), Some(OpenTip { reason: reason2, who: 20, @@ -476,7 +494,10 @@ fn genesis_funding_works() { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], }.assimilate_storage(&mut t).unwrap(); - pallet_treasury::GenesisConfig::default().assimilate_storage::(&mut t).unwrap(); + + pallet_treasury::GenesisConfig::::default() + .assimilate_storage(&mut t).unwrap(); + let mut t: sp_io::TestExternalities = t.into(); t.execute_with(|| { diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs index 119516fe2741a..fc567756f454f 100644 --- a/frame/treasury/src/benchmarking.rs +++ b/frame/treasury/src/benchmarking.rs @@ -22,15 +22,17 @@ use super::*; use frame_system::RawOrigin; -use frame_benchmarking::{benchmarks_instance, account, impl_benchmark_test_suite}; -use frame_support::traits::OnInitialize; - +use frame_benchmarking::{benchmarks_instance_pallet, account, impl_benchmark_test_suite}; +use frame_support::{ + ensure, + traits::{OnInitialize}, +}; use crate::Module as Treasury; const SEED: u32 = 0; // Create the pre-requisite information needed to create a treasury `propose_spend`. -fn setup_proposal, I: Instance>(u: u32) -> ( +fn setup_proposal, I: 'static>(u: u32) -> ( T::AccountId, BalanceOf, ::Source, @@ -44,7 +46,7 @@ fn setup_proposal, I: Instance>(u: u32) -> ( } // Create proposals that are approved for use in `on_initialize`. -fn create_approved_proposals, I: Instance>(n: u32) -> Result<(), &'static str> { +fn create_approved_proposals, I: 'static>(n: u32) -> Result<(), &'static str> { for i in 0 .. n { let (caller, value, lookup) = setup_proposal::(i); Treasury::::propose_spend( @@ -52,20 +54,20 @@ fn create_approved_proposals, I: Instance>(n: u32) -> Result<(), &' value, lookup )?; - let proposal_id = >::get() - 1; + let proposal_id = >::get() - 1; Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; } - ensure!(>::get().len() == n as usize, "Not all approved"); + ensure!(>::get().len() == n as usize, "Not all approved"); Ok(()) } -fn setup_pot_account, I: Instance>() { +fn setup_pot_account, I: 'static>() { let pot_account = Treasury::::account_id(); let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } -benchmarks_instance! { +benchmarks_instance_pallet! { propose_spend { let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index cef50706b5173..6795d7418b98c 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -61,74 +61,44 @@ mod tests; mod benchmarking; -pub mod weights; +use sp_std::prelude::*; #[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::traits::{ - Currency, Get, Imbalance, OnUnbalanced, ExistenceRequirement::{KeepAlive}, - ReservableCurrency, WithdrawReasons +use frame_support::{ + print, + traits::{ + Currency, Get, Imbalance, OnUnbalanced, ExistenceRequirement::{KeepAlive}, + ReservableCurrency, WithdrawReasons, + }, + weights::{Weight}, +}; +use sp_runtime::{ + Permill, ModuleId, RuntimeDebug, + traits::{ + Zero, StaticLookup, AccountIdConversion, Saturating + }, }; -use sp_runtime::{Permill, ModuleId, RuntimeDebug, traits::{ - Zero, StaticLookup, AccountIdConversion, Saturating -}}; -use frame_support::weights::{Weight, DispatchClass}; -use frame_support::traits::{EnsureOrigin}; +#[cfg(feature = "std")] +use frame_support::traits::GenesisBuild; + use codec::{Encode, Decode}; -use frame_system::{ensure_signed}; + +pub mod weights; pub use weights::WeightInfo; -pub type BalanceOf = +pub use pallet::*; + +/// An index of a proposal. Just a `u32`. +pub type ProposalIndex = u32; + +pub type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; -pub type PositiveImbalanceOf = +pub type PositiveImbalanceOf = <>::Currency as Currency<::AccountId>>::PositiveImbalance; -pub type NegativeImbalanceOf = +pub type NegativeImbalanceOf = <>::Currency as Currency<::AccountId>>::NegativeImbalance; -pub trait Config: frame_system::Config { - /// The treasury's module id, used for deriving its sovereign account ID. - type ModuleId: Get; - - /// The staking balance. - type Currency: Currency + ReservableCurrency; - - /// Origin from which approvals must come. - type ApproveOrigin: EnsureOrigin; - - /// Origin from which rejections must come. - type RejectOrigin: EnsureOrigin; - - /// The overarching event type. - type Event: From> + Into<::Event>; - - /// Handler for the unbalanced decrease when slashing for a rejected proposal or bounty. - type OnSlash: OnUnbalanced>; - - /// Fraction of a proposal's value that should be bonded in order to place the proposal. - /// An accepted proposal gets these back. A rejected proposal does not. - type ProposalBond: Get; - - /// Minimum amount of funds that should be placed in a deposit for making a proposal. - type ProposalBondMinimum: Get>; - - /// Period between successive spends. - type SpendPeriod: Get; - - /// Percentage of spare funds (if any) that are burnt per spend period. - type Burn: Get; - - /// Handler for the unbalanced decrease when treasury funds are burned. - type BurnDestination: OnUnbalanced>; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - - /// Runtime hooks to external pallet using treasury to compute spend funds. - type SpendFunds: SpendFunds; -} - /// A trait to allow the Treasury Pallet to spend it's funds for other purposes. /// There is an expectation that the implementer of this trait will correctly manage /// the mutable variables passed to it: @@ -142,7 +112,7 @@ pub trait Config: frame_system::Config { /// not enough funds, mark this value as `true`. This will prevent the treasury /// from burning the excess funds. #[impl_trait_for_tuples::impl_for_tuples(30)] -pub trait SpendFunds, I=DefaultInstance> { +pub trait SpendFunds, I: 'static = ()> { fn spend_funds( budget_remaining: &mut BalanceOf, imbalance: &mut PositiveImbalanceOf, @@ -151,9 +121,6 @@ pub trait SpendFunds, I=DefaultInstance> { ); } -/// 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)] @@ -168,108 +135,85 @@ pub struct Proposal { bond: Balance, } -decl_storage! { - trait Store for Module, I: Instance=DefaultInstance> as Treasury { - /// Number of proposals that have been made. - ProposalCount get(fn proposal_count): ProposalIndex; +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use super::*; - /// Proposals that have been made. - pub Proposals get(fn proposals): - map hasher(twox_64_concat) ProposalIndex - => Option>>; + #[pallet::config] + pub trait Config: frame_system::Config { - /// Proposal indices that have been approved but not yet awarded. - pub Approvals get(fn approvals): Vec; - } - add_extra_genesis { - build(|_config| { - // Create Treasury account - let account_id = >::account_id(); - let min = T::Currency::minimum_balance(); - if T::Currency::free_balance(&account_id) < min { - let _ = T::Currency::make_free_balance_be( - &account_id, - min, - ); - } - }); - } -} + /// The treasury's module id, used for deriving its sovereign account ID. + type ModuleId: Get; -decl_event!( - pub enum Event - where - Balance = BalanceOf, - ::AccountId, - { - /// New proposal. \[proposal_index\] - Proposed(ProposalIndex), - /// We have ended a spend period and will now allocate funds. \[budget_remaining\] - Spending(Balance), - /// Some funds have been allocated. \[proposal_index, award, beneficiary\] - Awarded(ProposalIndex, Balance, AccountId), - /// A proposal was rejected; funds were slashed. \[proposal_index, slashed\] - Rejected(ProposalIndex, Balance), - /// Some of our funds have been burnt. \[burn\] - Burnt(Balance), - /// Spending has finished; this is the amount that rolls over until next spend. - /// \[budget_remaining\] - Rollover(Balance), - /// Some funds have been deposited. \[deposit\] - Deposit(Balance), - } -); + /// The staking balance. + type Currency: Currency + ReservableCurrency; -decl_error! { - /// Error for the treasury module. - pub enum Error for Module, I: Instance> { - /// Proposer's balance is too low. - InsufficientProposersBalance, - /// No proposal or bounty at that index. - InvalidIndex, - } -} + /// Origin from which approvals must come. + type ApproveOrigin: EnsureOrigin; + + /// Origin from which rejections must come. + type RejectOrigin: EnsureOrigin; + + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// Handler for the unbalanced decrease when slashing for a rejected proposal or bounty. + type OnSlash: OnUnbalanced>; -decl_module! { - pub struct Module, I: Instance=DefaultInstance> - for enum Call - where origin: T::Origin - { /// Fraction of a proposal's value that should be bonded in order to place the proposal. /// An accepted proposal gets these back. A rejected proposal does not. - const ProposalBond: Permill = T::ProposalBond::get(); + type ProposalBond: Get; /// Minimum amount of funds that should be placed in a deposit for making a proposal. - const ProposalBondMinimum: BalanceOf = T::ProposalBondMinimum::get(); + type ProposalBondMinimum: Get>; /// Period between successive spends. - const SpendPeriod: T::BlockNumber = T::SpendPeriod::get(); + type SpendPeriod: Get; /// Percentage of spare funds (if any) that are burnt per spend period. - const Burn: Permill = T::Burn::get(); + type Burn: Get; - /// The treasury's module id, used for deriving its sovereign account ID. - const ModuleId: ModuleId = T::ModuleId::get(); + /// Handler for the unbalanced decrease when treasury funds are burned. + type BurnDestination: OnUnbalanced>; - type Error = Error; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; - fn deposit_event() = default; + /// Runtime hooks to external pallet using treasury to compute spend funds. + type SpendFunds: SpendFunds; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + /// `on_initialize` to return the weight used in `spend_funds`. + fn on_initialize(now: T::BlockNumber) -> Weight { + // Check to see if we should spend some funds! + if (now % T::SpendPeriod::get()).is_zero() { + Self::spend_funds() + } else { + 0 + } + } + } + + #[pallet::call] + impl, I: 'static> Pallet { /// 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. - /// - /// # - /// - Complexity: O(1) - /// - DbReads: `ProposalCount`, `origin account` - /// - DbWrites: `ProposalCount`, `Proposals`, `origin account` - /// # - #[weight = T::WeightInfo::propose_spend()] + #[pallet::weight(T::WeightInfo::propose_spend())] pub fn propose_spend( - origin, - #[compact] value: BalanceOf, + origin: OriginFor, + #[pallet::compact] value: BalanceOf, beneficiary: ::Source - ) { + ) -> DispatchResultWithPostInfo { let proposer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; @@ -278,23 +222,32 @@ decl_module! { .map_err(|_| Error::::InsufficientProposersBalance)?; let c = Self::proposal_count(); - >::put(c + 1); - >::insert(c, Proposal { proposer, value, beneficiary, bond }); - - Self::deposit_event(RawEvent::Proposed(c)); + >::put(c + 1); + >::insert( + c, + Proposal::>{ + proposer, + value, + beneficiary, + bond, + }, + ); + Self::deposit_event(Event::Proposed(c)); + Ok(().into()) } - /// Reject a proposed spend. The original deposit will be slashed. /// /// May only be called from `T::RejectOrigin`. - /// - /// # - /// - Complexity: O(1) - /// - DbReads: `Proposals`, `rejected proposer account` - /// - DbWrites: `Proposals`, `rejected proposer account` - /// # - #[weight = (T::WeightInfo::reject_proposal(), DispatchClass::Operational)] - pub fn reject_proposal(origin, #[compact] proposal_id: ProposalIndex) { + #[pallet::weight( + ( + T::WeightInfo::reject_proposal(), + DispatchClass::Operational, + ) + )] + pub fn reject_proposal( + origin: OriginFor, + #[pallet::compact] proposal_id: ProposalIndex + ) -> DispatchResultWithPostInfo { T::RejectOrigin::ensure_origin(origin)?; let proposal = >::take(&proposal_id).ok_or(Error::::InvalidIndex)?; @@ -303,46 +256,137 @@ decl_module! { T::OnSlash::on_unbalanced(imbalance); Self::deposit_event(Event::::Rejected(proposal_id, value)); + Ok(().into()) } - /// Approve a proposal. At a later time, the proposal will be allocated to the beneficiary /// and the original deposit will be returned. /// /// May only be called from `T::ApproveOrigin`. - /// - /// # - /// - Complexity: O(1). - /// - DbReads: `Proposals`, `Approvals` - /// - DbWrite: `Approvals` - /// # - #[weight = (T::WeightInfo::approve_proposal(), DispatchClass::Operational)] - pub fn approve_proposal(origin, #[compact] proposal_id: ProposalIndex) { + #[pallet::weight( + ( + T::WeightInfo::approve_proposal(), + DispatchClass::Operational + ) + )] + pub fn approve_proposal( + origin: OriginFor, + #[pallet::compact] proposal_id: ProposalIndex, + ) -> DispatchResultWithPostInfo { T::ApproveOrigin::ensure_origin(origin)?; ensure!(>::contains_key(proposal_id), Error::::InvalidIndex); - Approvals::::append(proposal_id); + >::append(proposal_id); + Ok(().into()) } + } - /// # - /// - 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. - /// # - 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() - } else { - 0 + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", BalanceOf = "Balance")] + pub enum Event, I: 'static = ()> { + /// New proposal. \[proposal_index\] + Proposed(ProposalIndex), + /// We have ended a spend period and will now allocate funds. \[budget_remaining\] + Spending(BalanceOf), + /// Some funds have been allocated. \[proposal_index, award, beneficiary\] + Awarded(ProposalIndex, BalanceOf, T::AccountId), + /// A proposal was rejected; funds were slashed. \[proposal_index, slashed\] + Rejected(ProposalIndex, BalanceOf), + /// Some of our funds have been burnt. \[burn\] + Burnt(BalanceOf), + /// Spending has finished; this is the amount that rolls over until next spend. + /// \[budget_remaining\] + Rollover(BalanceOf), + /// Some funds have been deposited. \[deposit\] + Deposit(BalanceOf), + } + + /// Old name generated by `decl_event`. + // TODO :: Have to re-check is "Note message" required ? + // #[deprecated(note = "use `Event` instead")] + pub type RawEvent = Event; + + #[pallet::error] + pub enum Error { + /// Proposer's balance is too low. + InsufficientProposersBalance, + /// No proposal or bounty at that index. + InvalidIndex, + } + + /// Number of proposals that have been made. + #[pallet::storage] + #[pallet::getter(fn proposal_count)] + pub type ProposalCount, I: 'static = ()> = StorageValue<_, ProposalIndex, ValueQuery>; + + /// Proposals that have been made. + #[pallet::storage] + #[pallet::getter(fn proposals)] + pub type Proposals, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + ProposalIndex, + Proposal>, + OptionQuery, + >; + + /// Proposal indices that have been approved but not yet awarded. + #[pallet::storage] + #[pallet::getter(fn approvals)] + pub type Approvals, I: 'static = ()> = + StorageValue<_, Vec, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()>{ + pub phantom: sp_std::marker::PhantomData<(T, I)>, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + phantom: Default::default(), } } } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + // Create Treasury account + let account_id = >::account_id(); + let min = T::Currency::minimum_balance(); + if T::Currency::free_balance(&account_id) < min { + let _ = T::Currency::make_free_balance_be( + &account_id, + min, + ); + } + } + } +} + +#[cfg(feature = "std")] +impl, I: 'static> GenesisConfig { + /// Direct implementation of `GenesisBuild::build_storage`. + /// + /// Kept in order not to break dependency. + pub fn build_storage(&self) -> Result { + >::build_storage(self) + } + + /// Direct implementation of `GenesisBuild::assimilate_storage`. + /// + /// Kept in order not to break dependency. + pub fn assimilate_storage( + &self, + storage: &mut sp_runtime::Storage + ) -> Result<(), String> { + >::assimilate_storage(self, storage) + } } -impl, I: Instance> Module { - // Add public immutables and private mutables. +impl, I: 'static> Pallet { /// The account ID of the treasury pot. /// @@ -367,7 +411,7 @@ impl, I: Instance> Module { let mut missed_any = false; let mut imbalance = >::zero(); - let proposals_len = Approvals::::mutate(|v| { + let proposals_len = Approvals::::mutate(|v| { let proposals_approvals_len = v.len() as u32; v.retain(|&index| { // Should always be true, but shouldn't panic if false or we're screwed. @@ -442,7 +486,7 @@ impl, I: Instance> Module { } -impl, I: Instance> OnUnbalanced> for Module { +impl, I: 'static> OnUnbalanced> for Pallet { fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { let numeric_amount = amount.peek(); diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index 45fc3e629fb0b..5294e68b0bd0d 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -15,12 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Treasury pallet tests. +//! Treasury test utilities #![cfg(test)] -use crate as treasury; -use super::*; +use crate::*; +use sp_std::{prelude::*}; + use std::cell::RefCell; use frame_support::{ assert_noop, assert_ok, parameter_types, @@ -29,11 +30,17 @@ use frame_support::{ use sp_core::H256; use sp_runtime::{ - ModuleId, + Permill, ModuleId, testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; +<<<<<<< HEAD +use crate::{ + self as treasury, + Config, +}; +======= type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -48,6 +55,7 @@ frame_support::construct_runtime!( Treasury: treasury::{Pallet, Call, Storage, Config, Event}, } ); +>>>>>>> upstream/master parameter_types! { pub const BlockHashCount: u64 = 250; @@ -119,14 +127,36 @@ impl Config for Test { type SpendFunds = (); } +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Module, Call, Storage, Config, Event}, + Balances: pallet_balances::{Module, Call, Storage, Config, Event}, + Treasury: treasury::{Module, Call, Storage, Config, Event}, + } +); + pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig::{ - // Total issuance will be 200 with treasury account initialized at ED. + + // Total issuance will be 200 with treasury account initialized at ED. + pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 98), (2, 1)], }.assimilate_storage(&mut t).unwrap(); - treasury::GenesisConfig::default().assimilate_storage::(&mut t).unwrap(); - t.into() + + treasury::GenesisConfig::::default() + .assimilate_storage(&mut t).unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + + ext } #[test] @@ -351,7 +381,7 @@ fn genesis_funding_works() { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], }.assimilate_storage(&mut t).unwrap(); - treasury::GenesisConfig::default().assimilate_storage::(&mut t).unwrap(); + treasury::GenesisConfig::::default().assimilate_storage(&mut t).unwrap(); let mut t: sp_io::TestExternalities = t.into(); t.execute_with(|| {