Bounties Pallet to FrameV2 (#9566)

* migrate bounties pallet

* events in tests

* test import event

* Update frame/bounties/src/lib.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* cargo fmt

* line width

* benchmarks compile

* add migrations

* fmt

* comments

* mod migrations

* fix Cargo.toml

* never remember cargo fmt

* fix migration

* migrations and test

* change checks in migration

* remove unused values

* Update frame/bounties/src/migrations/v4.rs

* cargo fmt

* fix benchmarking

* trigger ci

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
ferrell-code
2021-09-21 09:07:36 -04:00
committed by GitHub
parent e2dcb4b657
commit eca9af1a0a
7 changed files with 561 additions and 234 deletions
+1
View File
@@ -5121,6 +5121,7 @@ dependencies = [
"frame-benchmarking", "frame-benchmarking",
"frame-support", "frame-support",
"frame-system", "frame-system",
"log 0.4.14",
"pallet-balances", "pallet-balances",
"pallet-treasury", "pallet-treasury",
"parity-scale-codec", "parity-scale-codec",
+6 -3
View File
@@ -22,24 +22,27 @@ sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../pr
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" }
sp-io = { version = "4.0.0-dev", path = "../../primitives/io", default-features = false }
sp-core = { version = "4.0.0-dev", path = "../../primitives/core", default-features = false }
frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true }
log = { version = "0.4.14", default-features = false }
[dev-dependencies] [dev-dependencies]
sp-io = { version = "4.0.0-dev", path = "../../primitives/io" }
sp-core = { version = "4.0.0-dev", path = "../../primitives/core" }
pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-balances = { version = "4.0.0-dev", path = "../balances" }
[features] [features]
default = ["std"] default = ["std"]
std = [ std = [
"codec/std", "codec/std",
"sp-core/std",
"sp-io/std",
"scale-info/std", "scale-info/std",
"sp-std/std", "sp-std/std",
"sp-runtime/std", "sp-runtime/std",
"frame-support/std", "frame-support/std",
"frame-system/std", "frame-system/std",
"pallet-treasury/std", "pallet-treasury/std",
"log/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
"frame-benchmarking", "frame-benchmarking",
+16 -18
View File
@@ -22,11 +22,10 @@
use super::*; use super::*;
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
use frame_support::traits::OnInitialize;
use frame_system::RawOrigin; use frame_system::RawOrigin;
use sp_runtime::traits::Bounded; use sp_runtime::traits::Bounded;
use crate::Module as Bounties; use crate::Pallet as Bounties;
use pallet_treasury::Pallet as Treasury; use pallet_treasury::Pallet as Treasury;
const SEED: u32 = 0; const SEED: u32 = 0;
@@ -36,10 +35,10 @@ fn create_approved_bounties<T: Config>(n: u32) -> Result<(), &'static str> {
for i in 0..n { for i in 0..n {
let (caller, _curator, _fee, value, reason) = setup_bounty::<T>(i, MAX_BYTES); let (caller, _curator, _fee, value, reason) = setup_bounty::<T>(i, MAX_BYTES);
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?; Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?;
} }
ensure!(BountyApprovals::get().len() == n as usize, "Not all bounty approved"); ensure!(BountyApprovals::<T>::get().len() == n as usize, "Not all bounty approved");
Ok(()) Ok(())
} }
@@ -64,7 +63,7 @@ fn create_bounty<T: Config>(
let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES); let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES);
let curator_lookup = T::Lookup::unlookup(curator.clone()); let curator_lookup = T::Lookup::unlookup(curator.clone());
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?; Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?;
Treasury::<T>::on_initialize(T::BlockNumber::zero()); Treasury::<T>::on_initialize(T::BlockNumber::zero());
Bounties::<T>::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup.clone(), fee)?; Bounties::<T>::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup.clone(), fee)?;
@@ -94,7 +93,7 @@ benchmarks! {
approve_bounty { approve_bounty {
let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES); let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES);
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
}: _(RawOrigin::Root, bounty_id) }: _(RawOrigin::Root, bounty_id)
propose_curator { propose_curator {
@@ -102,7 +101,7 @@ benchmarks! {
let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES); let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES);
let curator_lookup = T::Lookup::unlookup(curator.clone()); let curator_lookup = T::Lookup::unlookup(curator.clone());
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?; Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?;
Bounties::<T>::on_initialize(T::BlockNumber::zero()); Bounties::<T>::on_initialize(T::BlockNumber::zero());
}: _(RawOrigin::Root, bounty_id, curator_lookup, fee) }: _(RawOrigin::Root, bounty_id, curator_lookup, fee)
@@ -112,7 +111,7 @@ benchmarks! {
setup_pot_account::<T>(); setup_pot_account::<T>();
let (curator_lookup, bounty_id) = create_bounty::<T>()?; let (curator_lookup, bounty_id) = create_bounty::<T>()?;
Bounties::<T>::on_initialize(T::BlockNumber::zero()); Bounties::<T>::on_initialize(T::BlockNumber::zero());
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
frame_system::Pallet::<T>::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into()); frame_system::Pallet::<T>::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into());
let caller = whitelisted_caller(); let caller = whitelisted_caller();
}: _(RawOrigin::Signed(caller), bounty_id) }: _(RawOrigin::Signed(caller), bounty_id)
@@ -122,7 +121,7 @@ benchmarks! {
let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES); let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, MAX_BYTES);
let curator_lookup = T::Lookup::unlookup(curator.clone()); let curator_lookup = T::Lookup::unlookup(curator.clone());
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?; Bounties::<T>::approve_bounty(RawOrigin::Root.into(), bounty_id)?;
Bounties::<T>::on_initialize(T::BlockNumber::zero()); Bounties::<T>::on_initialize(T::BlockNumber::zero());
Bounties::<T>::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup, fee)?; Bounties::<T>::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup, fee)?;
@@ -133,7 +132,7 @@ benchmarks! {
let (curator_lookup, bounty_id) = create_bounty::<T>()?; let (curator_lookup, bounty_id) = create_bounty::<T>()?;
Bounties::<T>::on_initialize(T::BlockNumber::zero()); Bounties::<T>::on_initialize(T::BlockNumber::zero());
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
let beneficiary = T::Lookup::unlookup(account("beneficiary", 0, SEED)); let beneficiary = T::Lookup::unlookup(account("beneficiary", 0, SEED));
@@ -144,10 +143,9 @@ benchmarks! {
let (curator_lookup, bounty_id) = create_bounty::<T>()?; let (curator_lookup, bounty_id) = create_bounty::<T>()?;
Bounties::<T>::on_initialize(T::BlockNumber::zero()); Bounties::<T>::on_initialize(T::BlockNumber::zero());
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED);
let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); let beneficiary = T::Lookup::unlookup(beneficiary_account.clone());
Bounties::<T>::award_bounty(RawOrigin::Signed(curator.clone()).into(), bounty_id, beneficiary)?; Bounties::<T>::award_bounty(RawOrigin::Signed(curator.clone()).into(), bounty_id, beneficiary)?;
@@ -164,17 +162,17 @@ benchmarks! {
setup_pot_account::<T>(); setup_pot_account::<T>();
let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, 0); let (caller, curator, fee, value, reason) = setup_bounty::<T>(0, 0);
Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; Bounties::<T>::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?;
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
}: close_bounty(RawOrigin::Root, bounty_id) }: close_bounty(RawOrigin::Root, bounty_id)
close_bounty_active { close_bounty_active {
setup_pot_account::<T>(); setup_pot_account::<T>();
let (curator_lookup, bounty_id) = create_bounty::<T>()?; let (curator_lookup, bounty_id) = create_bounty::<T>()?;
Bounties::<T>::on_initialize(T::BlockNumber::zero()); Bounties::<T>::on_initialize(T::BlockNumber::zero());
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
}: close_bounty(RawOrigin::Root, bounty_id) }: close_bounty(RawOrigin::Root, bounty_id)
verify { verify {
assert_last_event::<T>(RawEvent::BountyCanceled(bounty_id).into()) assert_last_event::<T>(Event::BountyCanceled(bounty_id).into())
} }
extend_bounty_expiry { extend_bounty_expiry {
@@ -182,11 +180,11 @@ benchmarks! {
let (curator_lookup, bounty_id) = create_bounty::<T>()?; let (curator_lookup, bounty_id) = create_bounty::<T>()?;
Bounties::<T>::on_initialize(T::BlockNumber::zero()); Bounties::<T>::on_initialize(T::BlockNumber::zero());
let bounty_id = BountyCount::get() - 1; let bounty_id = BountyCount::<T>::get() - 1;
let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
}: _(RawOrigin::Signed(curator), bounty_id, Vec::new()) }: _(RawOrigin::Signed(curator), bounty_id, Vec::new())
verify { verify {
assert_last_event::<T>(RawEvent::BountyExtended(bounty_id).into()) assert_last_event::<T>(Event::BountyExtended(bounty_id).into())
} }
spend_funds { spend_funds {
@@ -209,7 +207,7 @@ benchmarks! {
verify { verify {
ensure!(budget_remaining < BalanceOf::<T>::max_value(), "Budget not used"); ensure!(budget_remaining < BalanceOf::<T>::max_value(), "Budget not used");
ensure!(missed_any == false, "Missed some"); ensure!(missed_any == false, "Missed some");
assert_last_event::<T>(RawEvent::BountyBecameActive(b - 1).into()) assert_last_event::<T>(Event::BountyBecameActive(b - 1).into())
} }
} }
+202 -170
View File
@@ -75,13 +75,12 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking; mod benchmarking;
pub mod migrations;
mod tests; mod tests;
pub mod weights; pub mod weights;
use sp_std::prelude::*; use sp_std::prelude::*;
use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure};
use frame_support::traits::{ use frame_support::traits::{
Currency, ExistenceRequirement::AllowDeath, Get, Imbalance, OnUnbalanced, ReservableCurrency, Currency, ExistenceRequirement::AllowDeath, Get, Imbalance, OnUnbalanced, ReservableCurrency,
}; };
@@ -93,46 +92,17 @@ use sp_runtime::{
use frame_support::{dispatch::DispatchResultWithPostInfo, traits::EnsureOrigin}; use frame_support::{dispatch::DispatchResultWithPostInfo, traits::EnsureOrigin};
use frame_support::weights::Weight; use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use codec::{Decode, Encode};
use frame_system::{self as system, ensure_signed};
use scale_info::TypeInfo; use scale_info::TypeInfo;
pub use weights::WeightInfo; pub use weights::WeightInfo;
pub use pallet::*;
type BalanceOf<T> = pallet_treasury::BalanceOf<T>; type BalanceOf<T> = pallet_treasury::BalanceOf<T>;
type PositiveImbalanceOf<T> = pallet_treasury::PositiveImbalanceOf<T>; type PositiveImbalanceOf<T> = pallet_treasury::PositiveImbalanceOf<T>;
pub trait Config: frame_system::Config + pallet_treasury::Config {
/// The amount held on deposit for placing a bounty proposal.
type BountyDepositBase: Get<BalanceOf<Self>>;
/// The delay period for which a bounty beneficiary need to wait before claim the payout.
type BountyDepositPayoutDelay: Get<Self::BlockNumber>;
/// Bounty duration in blocks.
type BountyUpdatePeriod: Get<Self::BlockNumber>;
/// Percentage of the curator fee that will be reserved upfront as deposit for bounty curator.
type BountyCuratorDeposit: Get<Permill>;
/// Minimum value for a bounty.
type BountyValueMinimum: Get<BalanceOf<Self>>;
/// The amount held on deposit per byte within the tip report reason or bounty description.
type DataDepositPerByte: Get<BalanceOf<Self>>;
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
/// Maximum acceptable reason length.
type MaximumReasonLength: Get<u32>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
/// An index of a bounty. Just a `u32`. /// An index of a bounty. Just a `u32`.
pub type BountyIndex = u32; pub type BountyIndex = u32;
@@ -186,55 +156,54 @@ pub enum BountyStatus<AccountId, BlockNumber> {
}, },
} }
// Note :: For backward compatibility reasons, #[frame_support::pallet]
// pallet-bounties uses Treasury for storage. pub mod pallet {
// This is temporary solution, soon will get replaced with use super::*;
// Own storage identifier.
decl_storage! {
trait Store for Module<T: Config> as Treasury {
/// Number of bounty proposals that have been made. #[pallet::pallet]
pub BountyCount get(fn bounty_count): BountyIndex; #[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
/// Bounties that have been made. #[pallet::config]
pub Bounties get(fn bounties): pub trait Config: frame_system::Config + pallet_treasury::Config {
map hasher(twox_64_concat) BountyIndex /// The amount held on deposit for placing a bounty proposal.
=> Option<Bounty<T::AccountId, BalanceOf<T>, T::BlockNumber>>; #[pallet::constant]
type BountyDepositBase: Get<BalanceOf<Self>>;
/// The description of each bounty. /// The delay period for which a bounty beneficiary need to wait before claim the payout.
pub BountyDescriptions get(fn bounty_descriptions): map hasher(twox_64_concat) BountyIndex => Option<Vec<u8>>; #[pallet::constant]
type BountyDepositPayoutDelay: Get<Self::BlockNumber>;
/// Bounty indices that have been approved but not yet funded. /// Bounty duration in blocks.
pub BountyApprovals get(fn bounty_approvals): Vec<BountyIndex>; #[pallet::constant]
} type BountyUpdatePeriod: Get<Self::BlockNumber>;
/// Percentage of the curator fee that will be reserved upfront as deposit for bounty
/// curator.
#[pallet::constant]
type BountyCuratorDeposit: Get<Permill>;
/// Minimum value for a bounty.
#[pallet::constant]
type BountyValueMinimum: Get<BalanceOf<Self>>;
/// The amount held on deposit per byte within the tip report reason or bounty description.
#[pallet::constant]
type DataDepositPerByte: Get<BalanceOf<Self>>;
/// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// Maximum acceptable reason length.
#[pallet::constant]
type MaximumReasonLength: Get<u32>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
} }
decl_event!( #[pallet::error]
pub enum Event<T> pub enum Error<T> {
where
Balance = BalanceOf<T>,
<T as frame_system::Config>::AccountId,
{
/// New bounty proposal. \[index\]
BountyProposed(BountyIndex),
/// A bounty proposal was rejected; funds were slashed. \[index, bond\]
BountyRejected(BountyIndex, Balance),
/// A bounty proposal is funded and became active. \[index\]
BountyBecameActive(BountyIndex),
/// A bounty is awarded to a beneficiary. \[index, beneficiary\]
BountyAwarded(BountyIndex, AccountId),
/// A bounty is claimed by beneficiary. \[index, payout, beneficiary\]
BountyClaimed(BountyIndex, Balance, AccountId),
/// A bounty is cancelled. \[index\]
BountyCanceled(BountyIndex),
/// A bounty expiry is extended. \[index\]
BountyExtended(BountyIndex),
}
);
decl_error! {
/// Error for the treasury module.
pub enum Error for Module<T: Config> {
/// Proposer's balance is too low. /// Proposer's balance is too low.
InsufficientProposersBalance, InsufficientProposersBalance,
/// No proposal or bounty at that index. /// No proposal or bounty at that index.
@@ -255,38 +224,53 @@ decl_error! {
/// The bounties cannot be claimed/closed because it's still in the countdown period. /// The bounties cannot be claimed/closed because it's still in the countdown period.
Premature, Premature,
} }
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// New bounty proposal. \[index\]
BountyProposed(BountyIndex),
/// A bounty proposal was rejected; funds were slashed. \[index, bond\]
BountyRejected(BountyIndex, BalanceOf<T>),
/// A bounty proposal is funded and became active. \[index\]
BountyBecameActive(BountyIndex),
/// A bounty is awarded to a beneficiary. \[index, beneficiary\]
BountyAwarded(BountyIndex, T::AccountId),
/// A bounty is claimed by beneficiary. \[index, payout, beneficiary\]
BountyClaimed(BountyIndex, BalanceOf<T>, T::AccountId),
/// A bounty is cancelled. \[index\]
BountyCanceled(BountyIndex),
/// A bounty expiry is extended. \[index\]
BountyExtended(BountyIndex),
} }
decl_module! { /// Number of bounty proposals that have been made.
pub struct Module<T: Config> #[pallet::storage]
for enum Call #[pallet::getter(fn bounty_count)]
where origin: T::Origin pub type BountyCount<T: Config> = StorageValue<_, BountyIndex, ValueQuery>;
{
/// The amount held on deposit per byte within bounty description.
const DataDepositPerByte: BalanceOf<T> = T::DataDepositPerByte::get();
/// The amount held on deposit for placing a bounty proposal. /// Bounties that have been made.
const BountyDepositBase: BalanceOf<T> = T::BountyDepositBase::get(); #[pallet::storage]
#[pallet::getter(fn bounties)]
pub type Bounties<T: Config> = StorageMap<
_,
Twox64Concat,
BountyIndex,
Bounty<T::AccountId, BalanceOf<T>, T::BlockNumber>,
>;
/// The delay period for which a bounty beneficiary need to wait before claim the payout. /// The description of each bounty.
const BountyDepositPayoutDelay: T::BlockNumber = T::BountyDepositPayoutDelay::get(); #[pallet::storage]
#[pallet::getter(fn bounty_descriptions)]
pub type BountyDescriptions<T: Config> = StorageMap<_, Twox64Concat, BountyIndex, Vec<u8>>;
/// Bounty duration in blocks. /// Bounty indices that have been approved but not yet funded.
const BountyUpdatePeriod: T::BlockNumber = T::BountyUpdatePeriod::get(); #[pallet::storage]
#[pallet::getter(fn bounty_approvals)]
/// Percentage of the curator fee that will be reserved upfront as deposit for bounty curator. pub type BountyApprovals<T: Config> = StorageValue<_, Vec<BountyIndex>, ValueQuery>;
const BountyCuratorDeposit: Permill = T::BountyCuratorDeposit::get();
/// Minimum value for a bounty.
const BountyValueMinimum: BalanceOf<T> = T::BountyValueMinimum::get();
/// Maximum acceptable reason length.
const MaximumReasonLength: u32 = T::MaximumReasonLength::get();
type Error = Error<T>;
fn deposit_event() = default;
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Propose a new bounty. /// Propose a new bounty.
/// ///
/// The dispatch origin for this call must be _Signed_. /// The dispatch origin for this call must be _Signed_.
@@ -299,14 +283,15 @@ decl_module! {
/// - `fee`: The curator fee. /// - `fee`: The curator fee.
/// - `value`: The total payment amount of this bounty, curator fee included. /// - `value`: The total payment amount of this bounty, curator fee included.
/// - `description`: The description of this bounty. /// - `description`: The description of this bounty.
#[weight = <T as Config>::WeightInfo::propose_bounty(description.len() as u32)] #[pallet::weight(<T as Config>::WeightInfo::propose_bounty(description.len() as u32))]
fn propose_bounty( pub fn propose_bounty(
origin, origin: OriginFor<T>,
#[compact] value: BalanceOf<T>, #[pallet::compact] value: BalanceOf<T>,
description: Vec<u8>, description: Vec<u8>,
) { ) -> DispatchResult {
let proposer = ensure_signed(origin)?; let proposer = ensure_signed(origin)?;
Self::create_bounty(proposer, description, value)?; Self::create_bounty(proposer, description, value)?;
Ok(())
} }
/// Approve a bounty proposal. At a later time, the bounty will be funded and become active /// Approve a bounty proposal. At a later time, the bounty will be funded and become active
@@ -317,8 +302,11 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::approve_bounty()] #[pallet::weight(<T as Config>::WeightInfo::approve_bounty())]
fn approve_bounty(origin, #[compact] bounty_id: BountyIndex) { pub fn approve_bounty(
origin: OriginFor<T>,
#[pallet::compact] bounty_id: BountyIndex,
) -> DispatchResult {
T::ApproveOrigin::ensure_origin(origin)?; T::ApproveOrigin::ensure_origin(origin)?;
Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
@@ -327,10 +315,11 @@ decl_module! {
bounty.status = BountyStatus::Approved; bounty.status = BountyStatus::Approved;
BountyApprovals::append(bounty_id); BountyApprovals::<T>::append(bounty_id);
Ok(()) Ok(())
})?; })?;
Ok(())
} }
/// Assign a curator to a funded bounty. /// Assign a curator to a funded bounty.
@@ -340,18 +329,17 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::propose_curator()] #[pallet::weight(<T as Config>::WeightInfo::propose_curator())]
fn propose_curator( pub fn propose_curator(
origin, origin: OriginFor<T>,
#[compact] bounty_id: BountyIndex, #[pallet::compact] bounty_id: BountyIndex,
curator: <T::Lookup as StaticLookup>::Source, curator: <T::Lookup as StaticLookup>::Source,
#[compact] fee: BalanceOf<T>, #[pallet::compact] fee: BalanceOf<T>,
) { ) -> DispatchResult {
T::ApproveOrigin::ensure_origin(origin)?; T::ApproveOrigin::ensure_origin(origin)?;
let curator = T::Lookup::lookup(curator)?; let curator = T::Lookup::lookup(curator)?;
Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
let mut bounty = maybe_bounty.as_mut().ok_or(Error::<T>::InvalidIndex)?; let mut bounty = maybe_bounty.as_mut().ok_or(Error::<T>::InvalidIndex)?;
match bounty.status { match bounty.status {
BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => {}, BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => {},
@@ -365,14 +353,15 @@ decl_module! {
Ok(()) Ok(())
})?; })?;
Ok(())
} }
/// Unassign curator from a bounty. /// Unassign curator from a bounty.
/// ///
/// This function can only be called by the `RejectOrigin` a signed origin. /// This function can only be called by the `RejectOrigin` a signed origin.
/// ///
/// If this function is called by the `RejectOrigin`, we assume that the curator is malicious /// If this function is called by the `RejectOrigin`, we assume that the curator is
/// or inactive. As a result, we will slash the curator when possible. /// malicious or inactive. As a result, we will slash the curator when possible.
/// ///
/// If the origin is the curator, we take this as a sign they are unable to do their job and /// If the origin is the curator, we take this as a sign they are unable to do their job and
/// they willingly give up. We could slash them, but for now we allow them to recover their /// they willingly give up. We could slash them, but for now we allow them to recover their
@@ -385,11 +374,11 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::unassign_curator()] #[pallet::weight(<T as Config>::WeightInfo::unassign_curator())]
fn unassign_curator( pub fn unassign_curator(
origin, origin: OriginFor<T>,
#[compact] bounty_id: BountyIndex, #[pallet::compact] bounty_id: BountyIndex,
) { ) -> DispatchResult {
let maybe_sender = ensure_signed(origin.clone()) let maybe_sender = ensure_signed(origin.clone())
.map(Some) .map(Some)
.or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?; .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
@@ -407,7 +396,7 @@ decl_module! {
BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => { BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => {
// No curator to unassign at this point. // No curator to unassign at this point.
return Err(Error::<T>::UnexpectedStatus.into()) return Err(Error::<T>::UnexpectedStatus.into())
} },
BountyStatus::CuratorProposed { ref curator } => { BountyStatus::CuratorProposed { ref curator } => {
// A curator has been proposed, but not accepted yet. // A curator has been proposed, but not accepted yet.
// Either `RejectOrigin` or the proposed curator can unassign the curator. // Either `RejectOrigin` or the proposed curator can unassign the curator.
@@ -425,7 +414,7 @@ decl_module! {
// If the sender is not the curator, and the curator is inactive, // If the sender is not the curator, and the curator is inactive,
// slash the curator. // slash the curator.
if sender != *curator { if sender != *curator {
let block_number = system::Pallet::<T>::block_number(); let block_number = frame_system::Pallet::<T>::block_number();
if *update_due < block_number { if *update_due < block_number {
slash_curator(curator, &mut bounty.curator_deposit); slash_curator(curator, &mut bounty.curator_deposit);
// Continue to change bounty status below... // Continue to change bounty status below...
@@ -436,7 +425,8 @@ decl_module! {
} else { } else {
// Else this is the curator, willingly giving up their role. // Else this is the curator, willingly giving up their role.
// Give back their deposit. // Give back their deposit.
let err_amount = T::Currency::unreserve(&curator, bounty.curator_deposit); let err_amount =
T::Currency::unreserve(&curator, bounty.curator_deposit);
debug_assert!(err_amount.is_zero()); debug_assert!(err_amount.is_zero());
// Continue to change bounty status below... // Continue to change bounty status below...
} }
@@ -450,12 +440,13 @@ decl_module! {
ensure!(maybe_sender.is_none(), BadOrigin); ensure!(maybe_sender.is_none(), BadOrigin);
slash_curator(curator, &mut bounty.curator_deposit); slash_curator(curator, &mut bounty.curator_deposit);
// Continue to change bounty status below... // Continue to change bounty status below...
} },
}; };
bounty.status = BountyStatus::Funded; bounty.status = BountyStatus::Funded;
Ok(()) Ok(())
})?; })?;
Ok(())
} }
/// Accept the curator role for a bounty. /// Accept the curator role for a bounty.
@@ -466,8 +457,11 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::accept_curator()] #[pallet::weight(<T as Config>::WeightInfo::accept_curator())]
fn accept_curator(origin, #[compact] bounty_id: BountyIndex) { pub fn accept_curator(
origin: OriginFor<T>,
#[pallet::compact] bounty_id: BountyIndex,
) -> DispatchResult {
let signer = ensure_signed(origin)?; let signer = ensure_signed(origin)?;
Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
@@ -481,17 +475,21 @@ decl_module! {
T::Currency::reserve(curator, deposit)?; T::Currency::reserve(curator, deposit)?;
bounty.curator_deposit = deposit; bounty.curator_deposit = deposit;
let update_due = system::Pallet::<T>::block_number() + T::BountyUpdatePeriod::get(); let update_due = frame_system::Pallet::<T>::block_number() +
bounty.status = BountyStatus::Active { curator: curator.clone(), update_due }; T::BountyUpdatePeriod::get();
bounty.status =
BountyStatus::Active { curator: curator.clone(), update_due };
Ok(()) Ok(())
}, },
_ => Err(Error::<T>::UnexpectedStatus.into()), _ => Err(Error::<T>::UnexpectedStatus.into()),
} }
})?; })?;
Ok(())
} }
/// Award bounty to a beneficiary account. The beneficiary will be able to claim the funds after a delay. /// Award bounty to a beneficiary account. The beneficiary will be able to claim the funds
/// after a delay.
/// ///
/// The dispatch origin for this call must be the curator of this bounty. /// The dispatch origin for this call must be the curator of this bounty.
/// ///
@@ -501,18 +499,19 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::award_bounty()] #[pallet::weight(<T as Config>::WeightInfo::award_bounty())]
fn award_bounty(origin, #[compact] bounty_id: BountyIndex, beneficiary: <T::Lookup as StaticLookup>::Source) { pub fn award_bounty(
origin: OriginFor<T>,
#[pallet::compact] bounty_id: BountyIndex,
beneficiary: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let signer = ensure_signed(origin)?; let signer = ensure_signed(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?; let beneficiary = T::Lookup::lookup(beneficiary)?;
Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
let mut bounty = maybe_bounty.as_mut().ok_or(Error::<T>::InvalidIndex)?; let mut bounty = maybe_bounty.as_mut().ok_or(Error::<T>::InvalidIndex)?;
match &bounty.status { match &bounty.status {
BountyStatus::Active { BountyStatus::Active { curator, .. } => {
curator,
..
} => {
ensure!(signer == *curator, Error::<T>::RequireCurator); ensure!(signer == *curator, Error::<T>::RequireCurator);
}, },
_ => return Err(Error::<T>::UnexpectedStatus.into()), _ => return Err(Error::<T>::UnexpectedStatus.into()),
@@ -520,13 +519,15 @@ decl_module! {
bounty.status = BountyStatus::PendingPayout { bounty.status = BountyStatus::PendingPayout {
curator: signer, curator: signer,
beneficiary: beneficiary.clone(), beneficiary: beneficiary.clone(),
unlock_at: system::Pallet::<T>::block_number() + T::BountyDepositPayoutDelay::get(), unlock_at: frame_system::Pallet::<T>::block_number() +
T::BountyDepositPayoutDelay::get(),
}; };
Ok(()) Ok(())
})?; })?;
Self::deposit_event(Event::<T>::BountyAwarded(bounty_id, beneficiary)); Self::deposit_event(Event::<T>::BountyAwarded(bounty_id, beneficiary));
Ok(())
} }
/// Claim the payout from an awarded bounty after payout delay. /// Claim the payout from an awarded bounty after payout delay.
@@ -538,14 +539,22 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::claim_bounty()] #[pallet::weight(<T as Config>::WeightInfo::claim_bounty())]
fn claim_bounty(origin, #[compact] bounty_id: BountyIndex) { pub fn claim_bounty(
origin: OriginFor<T>,
#[pallet::compact] bounty_id: BountyIndex,
) -> DispatchResult {
let _ = ensure_signed(origin)?; // anyone can trigger claim let _ = ensure_signed(origin)?; // anyone can trigger claim
Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
let bounty = maybe_bounty.take().ok_or(Error::<T>::InvalidIndex)?; let bounty = maybe_bounty.take().ok_or(Error::<T>::InvalidIndex)?;
if let BountyStatus::PendingPayout { curator, beneficiary, unlock_at } = bounty.status { if let BountyStatus::PendingPayout { curator, beneficiary, unlock_at } =
ensure!(system::Pallet::<T>::block_number() >= unlock_at, Error::<T>::Premature); bounty.status
{
ensure!(
frame_system::Pallet::<T>::block_number() >= unlock_at,
Error::<T>::Premature
);
let bounty_account = Self::bounty_account_id(bounty_id); let bounty_account = Self::bounty_account_id(bounty_id);
let balance = T::Currency::free_balance(&bounty_account); let balance = T::Currency::free_balance(&bounty_account);
let fee = bounty.fee.min(balance); // just to be safe let fee = bounty.fee.min(balance); // just to be safe
@@ -554,12 +563,13 @@ decl_module! {
debug_assert!(err_amount.is_zero()); debug_assert!(err_amount.is_zero());
let res = T::Currency::transfer(&bounty_account, &curator, fee, AllowDeath); // should not fail let res = T::Currency::transfer(&bounty_account, &curator, fee, AllowDeath); // should not fail
debug_assert!(res.is_ok()); debug_assert!(res.is_ok());
let res = T::Currency::transfer(&bounty_account, &beneficiary, payout, AllowDeath); // should not fail let res =
T::Currency::transfer(&bounty_account, &beneficiary, payout, AllowDeath); // should not fail
debug_assert!(res.is_ok()); debug_assert!(res.is_ok());
*maybe_bounty = None; *maybe_bounty = None;
BountyDescriptions::remove(bounty_id); BountyDescriptions::<T>::remove(bounty_id);
Self::deposit_event(Event::<T>::BountyClaimed(bounty_id, payout, beneficiary)); Self::deposit_event(Event::<T>::BountyClaimed(bounty_id, payout, beneficiary));
Ok(()) Ok(())
@@ -567,6 +577,7 @@ decl_module! {
Err(Error::<T>::UnexpectedStatus.into()) Err(Error::<T>::UnexpectedStatus.into())
} }
})?; })?;
Ok(())
} }
/// Cancel a proposed or active bounty. All the funds will be sent to treasury and /// Cancel a proposed or active bounty. All the funds will be sent to treasury and
@@ -579,17 +590,23 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::close_bounty_proposed().max(<T as Config>::WeightInfo::close_bounty_active())] #[pallet::weight(<T as Config>::WeightInfo::close_bounty_proposed()
fn close_bounty(origin, #[compact] bounty_id: BountyIndex) -> DispatchResultWithPostInfo { .max(<T as Config>::WeightInfo::close_bounty_active()))]
pub fn close_bounty(
origin: OriginFor<T>,
#[pallet::compact] bounty_id: BountyIndex,
) -> DispatchResultWithPostInfo {
T::RejectOrigin::ensure_origin(origin)?; T::RejectOrigin::ensure_origin(origin)?;
Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResultWithPostInfo { Bounties::<T>::try_mutate_exists(
bounty_id,
|maybe_bounty| -> DispatchResultWithPostInfo {
let bounty = maybe_bounty.as_ref().ok_or(Error::<T>::InvalidIndex)?; let bounty = maybe_bounty.as_ref().ok_or(Error::<T>::InvalidIndex)?;
match &bounty.status { match &bounty.status {
BountyStatus::Proposed => { BountyStatus::Proposed => {
// The reject origin would like to cancel a proposed bounty. // The reject origin would like to cancel a proposed bounty.
BountyDescriptions::remove(bounty_id); BountyDescriptions::<T>::remove(bounty_id);
let value = bounty.bond; let value = bounty.bond;
let imbalance = T::Currency::slash_reserved(&bounty.proposer, value).0; let imbalance = T::Currency::slash_reserved(&bounty.proposer, value).0;
T::OnSlash::on_unbalanced(imbalance); T::OnSlash::on_unbalanced(imbalance);
@@ -597,20 +614,22 @@ decl_module! {
Self::deposit_event(Event::<T>::BountyRejected(bounty_id, value)); Self::deposit_event(Event::<T>::BountyRejected(bounty_id, value));
// Return early, nothing else to do. // Return early, nothing else to do.
return Ok(Some(<T as Config>::WeightInfo::close_bounty_proposed()).into()) return Ok(
Some(<T as Config>::WeightInfo::close_bounty_proposed()).into()
)
}, },
BountyStatus::Approved => { BountyStatus::Approved => {
// For weight reasons, we don't allow a council to cancel in this phase. // For weight reasons, we don't allow a council to cancel in this phase.
// We ask for them to wait until it is funded before they can cancel. // We ask for them to wait until it is funded before they can cancel.
return Err(Error::<T>::UnexpectedStatus.into()) return Err(Error::<T>::UnexpectedStatus.into())
}, },
BountyStatus::Funded | BountyStatus::Funded | BountyStatus::CuratorProposed { .. } => {
BountyStatus::CuratorProposed { .. } => {
// Nothing extra to do besides the removal of the bounty below. // Nothing extra to do besides the removal of the bounty below.
}, },
BountyStatus::Active { curator, .. } => { BountyStatus::Active { curator, .. } => {
// Cancelled by council, refund deposit of the working curator. // Cancelled by council, refund deposit of the working curator.
let err_amount = T::Currency::unreserve(&curator, bounty.curator_deposit); let err_amount =
T::Currency::unreserve(&curator, bounty.curator_deposit);
debug_assert!(err_amount.is_zero()); debug_assert!(err_amount.is_zero());
// Then execute removal of the bounty below. // Then execute removal of the bounty below.
}, },
@@ -620,21 +639,27 @@ decl_module! {
// So the council should first unassign the curator, slashing their // So the council should first unassign the curator, slashing their
// deposit. // deposit.
return Err(Error::<T>::PendingPayout.into()) return Err(Error::<T>::PendingPayout.into())
} },
} }
let bounty_account = Self::bounty_account_id(bounty_id); let bounty_account = Self::bounty_account_id(bounty_id);
BountyDescriptions::remove(bounty_id); BountyDescriptions::<T>::remove(bounty_id);
let balance = T::Currency::free_balance(&bounty_account); let balance = T::Currency::free_balance(&bounty_account);
let res = T::Currency::transfer(&bounty_account, &Self::account_id(), balance, AllowDeath); // should not fail let res = T::Currency::transfer(
&bounty_account,
&Self::account_id(),
balance,
AllowDeath,
); // should not fail
debug_assert!(res.is_ok()); debug_assert!(res.is_ok());
*maybe_bounty = None; *maybe_bounty = None;
Self::deposit_event(Event::<T>::BountyCanceled(bounty_id)); Self::deposit_event(Event::<T>::BountyCanceled(bounty_id));
Ok(Some(<T as Config>::WeightInfo::close_bounty_active()).into()) Ok(Some(<T as Config>::WeightInfo::close_bounty_active()).into())
}) },
)
} }
/// Extend the expiry time of an active bounty. /// Extend the expiry time of an active bounty.
@@ -647,8 +672,12 @@ decl_module! {
/// # <weight> /// # <weight>
/// - O(1). /// - O(1).
/// # </weight> /// # </weight>
#[weight = <T as Config>::WeightInfo::extend_bounty_expiry()] #[pallet::weight(<T as Config>::WeightInfo::extend_bounty_expiry())]
fn extend_bounty_expiry(origin, #[compact] bounty_id: BountyIndex, _remark: Vec<u8>) { pub fn extend_bounty_expiry(
origin: OriginFor<T>,
#[pallet::compact] bounty_id: BountyIndex,
_remark: Vec<u8>,
) -> DispatchResult {
let signer = ensure_signed(origin)?; let signer = ensure_signed(origin)?;
Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { Bounties::<T>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
@@ -657,7 +686,9 @@ decl_module! {
match bounty.status { match bounty.status {
BountyStatus::Active { ref curator, ref mut update_due } => { BountyStatus::Active { ref curator, ref mut update_due } => {
ensure!(*curator == signer, Error::<T>::RequireCurator); ensure!(*curator == signer, Error::<T>::RequireCurator);
*update_due = (system::Pallet::<T>::block_number() + T::BountyUpdatePeriod::get()).max(*update_due); *update_due = (frame_system::Pallet::<T>::block_number() +
T::BountyUpdatePeriod::get())
.max(*update_due);
}, },
_ => return Err(Error::<T>::UnexpectedStatus.into()), _ => return Err(Error::<T>::UnexpectedStatus.into()),
} }
@@ -666,11 +697,12 @@ decl_module! {
})?; })?;
Self::deposit_event(Event::<T>::BountyExtended(bounty_id)); Self::deposit_event(Event::<T>::BountyExtended(bounty_id));
Ok(())
} }
} }
} }
impl<T: Config> Module<T> { impl<T: Config> Pallet<T> {
// Add public immutables and private mutables. // Add public immutables and private mutables.
/// The account ID of the treasury pot. /// The account ID of the treasury pot.
@@ -707,7 +739,7 @@ impl<T: Config> Module<T> {
T::Currency::reserve(&proposer, bond) T::Currency::reserve(&proposer, bond)
.map_err(|_| Error::<T>::InsufficientProposersBalance)?; .map_err(|_| Error::<T>::InsufficientProposersBalance)?;
BountyCount::put(index + 1); BountyCount::<T>::put(index + 1);
let bounty = Bounty { let bounty = Bounty {
proposer, proposer,
@@ -719,22 +751,22 @@ impl<T: Config> Module<T> {
}; };
Bounties::<T>::insert(index, &bounty); Bounties::<T>::insert(index, &bounty);
BountyDescriptions::insert(index, description); BountyDescriptions::<T>::insert(index, description);
Self::deposit_event(RawEvent::BountyProposed(index)); Self::deposit_event(Event::<T>::BountyProposed(index));
Ok(()) Ok(())
} }
} }
impl<T: Config> pallet_treasury::SpendFunds<T> for Module<T> { impl<T: Config> pallet_treasury::SpendFunds<T> for Pallet<T> {
fn spend_funds( fn spend_funds(
budget_remaining: &mut BalanceOf<T>, budget_remaining: &mut BalanceOf<T>,
imbalance: &mut PositiveImbalanceOf<T>, imbalance: &mut PositiveImbalanceOf<T>,
total_weight: &mut Weight, total_weight: &mut Weight,
missed_any: &mut bool, missed_any: &mut bool,
) { ) {
let bounties_len = BountyApprovals::mutate(|v| { let bounties_len = BountyApprovals::<T>::mutate(|v| {
let bounties_approval_len = v.len() as u32; let bounties_approval_len = v.len() as u32;
v.retain(|&index| { v.retain(|&index| {
Bounties::<T>::mutate(index, |bounty| { Bounties::<T>::mutate(index, |bounty| {
@@ -755,7 +787,7 @@ impl<T: Config> pallet_treasury::SpendFunds<T> for Module<T> {
bounty.value, bounty.value,
)); ));
Self::deposit_event(RawEvent::BountyBecameActive(index)); Self::deposit_event(Event::<T>::BountyBecameActive(index));
false false
} else { } else {
*missed_any = true; *missed_any = true;
@@ -0,0 +1,19 @@
// This file is part of Substrate.
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Version 4.
pub mod v4;
@@ -0,0 +1,230 @@
// This file is part of Substrate.
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use frame_support::{
storage::{generator::StorageValue, StoragePrefixedMap},
traits::{
Get, GetStorageVersion, PalletInfoAccess, StorageVersion,
STORAGE_VERSION_STORAGE_KEY_POSTFIX,
},
weights::Weight,
};
use sp_core::hexdisplay::HexDisplay;
use sp_io::{hashing::twox_128, storage};
use sp_std::str;
use crate as pallet_bounties;
/// Migrate the storage of the bounties pallet to a new prefix, leaving all other storage untouched
///
/// This new prefix must be the same as the one set in construct_runtime. For safety, use
/// `PalletInfo` to get it, as:
/// `<Runtime as frame_system::Config>::PalletInfo::name::<BountiesPallet>`.
///
/// The migration will look into the storage version in order not to trigger a migration on an up
/// to date storage. Thus the on chain storage version must be less than 4 in order to trigger the
/// migration.
pub fn migrate<
T: pallet_bounties::Config,
P: GetStorageVersion + PalletInfoAccess,
N: AsRef<str>,
>(
old_pallet_name: N,
new_pallet_name: N,
) -> Weight {
let old_pallet_name = old_pallet_name.as_ref();
let new_pallet_name = new_pallet_name.as_ref();
if new_pallet_name == old_pallet_name {
log::info!(
target: "runtime::bounties",
"New pallet name is equal to the old prefix. No migration needs to be done.",
);
return 0
}
let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
log::info!(
target: "runtime::bounties",
"Running migration to v4 for bounties with storage version {:?}",
on_chain_storage_version,
);
if on_chain_storage_version < 4 {
let storage_prefix = pallet_bounties::BountyCount::<T>::storage_prefix();
frame_support::storage::migration::move_storage_from_pallet(
storage_prefix,
old_pallet_name.as_bytes(),
new_pallet_name.as_bytes(),
);
log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name);
let storage_prefix = pallet_bounties::Bounties::<T>::storage_prefix();
frame_support::storage::migration::move_storage_from_pallet(
storage_prefix,
old_pallet_name.as_bytes(),
new_pallet_name.as_bytes(),
);
log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name);
let storage_prefix = pallet_bounties::BountyDescriptions::<T>::storage_prefix();
frame_support::storage::migration::move_storage_from_pallet(
storage_prefix,
old_pallet_name.as_bytes(),
new_pallet_name.as_bytes(),
);
log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name);
let storage_prefix = pallet_bounties::BountyApprovals::<T>::storage_prefix();
frame_support::storage::migration::move_storage_from_pallet(
storage_prefix,
old_pallet_name.as_bytes(),
new_pallet_name.as_bytes(),
);
log_migration("migration", storage_prefix, old_pallet_name, new_pallet_name);
StorageVersion::new(4).put::<P>();
<T as frame_system::Config>::BlockWeights::get().max_block
} else {
log::warn!(
target: "runtime::bounties",
"Attempted to apply migration to v4 but failed because storage version is {:?}",
on_chain_storage_version,
);
0
}
}
/// Some checks prior to migration. This can be linked to
/// [`frame_support::traits::OnRuntimeUpgrade::pre_upgrade`] for further testing.
///
/// Panics if anything goes wrong.
pub fn pre_migration<T: pallet_bounties::Config, P: GetStorageVersion + 'static, N: AsRef<str>>(
old_pallet_name: N,
new_pallet_name: N,
) {
let old_pallet_name = old_pallet_name.as_ref();
let new_pallet_name = new_pallet_name.as_ref();
let storage_prefix_bounties_count = pallet_bounties::BountyCount::<T>::storage_prefix();
let storage_prefix_bounties = pallet_bounties::Bounties::<T>::storage_prefix();
let storage_prefix_bounties_description =
pallet_bounties::BountyDescriptions::<T>::storage_prefix();
let storage_prefix_bounties_approvals = pallet_bounties::BountyApprovals::<T>::storage_prefix();
log_migration("pre-migration", storage_prefix_bounties_count, old_pallet_name, new_pallet_name);
log_migration("pre-migration", storage_prefix_bounties, old_pallet_name, new_pallet_name);
log_migration(
"pre-migration",
storage_prefix_bounties_description,
old_pallet_name,
new_pallet_name,
);
log_migration(
"pre-migration",
storage_prefix_bounties_approvals,
old_pallet_name,
new_pallet_name,
);
let new_pallet_prefix = twox_128(new_pallet_name.as_bytes());
let storage_version_key =
[&new_pallet_prefix, &twox_128(STORAGE_VERSION_STORAGE_KEY_POSTFIX)[..]].concat();
// ensure nothing is stored in the new prefix.
assert!(
storage::next_key(&new_pallet_prefix).map_or(
// either nothing is there
true,
// or we ensure that the next key has no common prefix with twox_128(new),
// or is the pallet version that is already stored using the pallet name
|next_key| {
storage::next_key(&next_key).map_or(true, |next_key| {
!next_key.starts_with(&new_pallet_prefix) || next_key == storage_version_key
})
},
),
"unexpected next_key({}) = {:?}",
new_pallet_name,
HexDisplay::from(&sp_io::storage::next_key(&new_pallet_prefix).unwrap()),
);
assert!(<P as GetStorageVersion>::on_chain_storage_version() < 4);
}
/// Some checks for after migration. This can be linked to
/// [`frame_support::traits::OnRuntimeUpgrade::post_upgrade`] for further testing.
///
/// Panics if anything goes wrong.
pub fn post_migration<T: pallet_bounties::Config, P: GetStorageVersion, N: AsRef<str>>(
old_pallet_name: N,
new_pallet_name: N,
) {
let old_pallet_name = old_pallet_name.as_ref();
let new_pallet_name = new_pallet_name.as_ref();
let storage_prefix_bounties_count = pallet_bounties::BountyCount::<T>::storage_prefix();
let storage_prefix_bounties = pallet_bounties::Bounties::<T>::storage_prefix();
let storage_prefix_bounties_description =
pallet_bounties::BountyDescriptions::<T>::storage_prefix();
let storage_prefix_bounties_approvals = pallet_bounties::BountyApprovals::<T>::storage_prefix();
log_migration(
"post-migration",
storage_prefix_bounties_count,
old_pallet_name,
new_pallet_name,
);
log_migration("post-migration", storage_prefix_bounties, old_pallet_name, new_pallet_name);
log_migration(
"post-migration",
storage_prefix_bounties_description,
old_pallet_name,
new_pallet_name,
);
log_migration(
"post-migration",
storage_prefix_bounties_approvals,
old_pallet_name,
new_pallet_name,
);
let old_pallet_prefix = twox_128(old_pallet_name.as_bytes());
let old_bounties_count_key =
[&old_pallet_prefix, &twox_128(storage_prefix_bounties_count)[..]].concat();
let old_bounties_key = [&old_pallet_prefix, &twox_128(storage_prefix_bounties)[..]].concat();
let old_bounties_description_key =
[&old_pallet_prefix, &twox_128(storage_prefix_bounties_description)[..]].concat();
let old_bounties_approvals_key =
[&old_pallet_prefix, &twox_128(storage_prefix_bounties_approvals)[..]].concat();
assert!(storage::next_key(&old_bounties_count_key)
.map_or(true, |next_key| !next_key.starts_with(&old_bounties_count_key)));
assert!(storage::next_key(&old_bounties_key)
.map_or(true, |next_key| !next_key.starts_with(&old_bounties_key)));
assert!(storage::next_key(&old_bounties_description_key)
.map_or(true, |next_key| !next_key.starts_with(&old_bounties_description_key)));
assert!(storage::next_key(&old_bounties_approvals_key)
.map_or(true, |next_key| !next_key.starts_with(&old_bounties_approvals_key)));
assert_eq!(<P as GetStorageVersion>::on_chain_storage_version(), 4);
}
fn log_migration(stage: &str, storage_prefix: &[u8], old_pallet_name: &str, new_pallet_name: &str) {
log::info!(
target: "runtime::bounties",
"{} prefix of storage '{}': '{}' ==> '{}'",
stage,
str::from_utf8(storage_prefix).unwrap_or("<Invalid UTF8>"),
old_pallet_name,
new_pallet_name,
);
}
+51 -7
View File
@@ -32,9 +32,11 @@ use sp_core::H256;
use sp_runtime::{ use sp_runtime::{
testing::Header, testing::Header,
traits::{BadOrigin, BlakeTwo256, IdentityLookup}, traits::{BadOrigin, BlakeTwo256, IdentityLookup},
Perbill, Perbill, Storage,
}; };
use super::Event as BountiesEvent;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
@@ -160,7 +162,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
t.into() t.into()
} }
fn last_event() -> RawEvent<u64, u128> { fn last_event() -> BountiesEvent<Test> {
System::events() System::events()
.into_iter() .into_iter()
.map(|r| r.event) .map(|r| r.event)
@@ -396,7 +398,7 @@ fn propose_bounty_works() {
assert_ok!(Bounties::propose_bounty(Origin::signed(0), 10, b"1234567890".to_vec())); assert_ok!(Bounties::propose_bounty(Origin::signed(0), 10, b"1234567890".to_vec()));
assert_eq!(last_event(), RawEvent::BountyProposed(0)); assert_eq!(last_event(), BountiesEvent::BountyProposed(0));
let deposit: u64 = 85 + 5; let deposit: u64 = 85 + 5;
assert_eq!(Balances::reserved_balance(0), deposit); assert_eq!(Balances::reserved_balance(0), deposit);
@@ -458,7 +460,7 @@ fn close_bounty_works() {
let deposit: u64 = 80 + 5; let deposit: u64 = 80 + 5;
assert_eq!(last_event(), RawEvent::BountyRejected(0, deposit)); assert_eq!(last_event(), BountiesEvent::BountyRejected(0, deposit));
assert_eq!(Balances::reserved_balance(0), 0); assert_eq!(Balances::reserved_balance(0), 0);
assert_eq!(Balances::free_balance(0), 100 - deposit); assert_eq!(Balances::free_balance(0), 100 - deposit);
@@ -690,7 +692,7 @@ fn award_and_claim_bounty_works() {
assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0)); assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0));
assert_eq!(last_event(), RawEvent::BountyClaimed(0, 56, 3)); assert_eq!(last_event(), BountiesEvent::BountyClaimed(0, 56, 3));
assert_eq!(Balances::free_balance(4), 14); // initial 10 + fee 4 assert_eq!(Balances::free_balance(4), 14); // initial 10 + fee 4
@@ -729,7 +731,7 @@ fn claim_handles_high_fee() {
assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0)); assert_ok!(Bounties::claim_bounty(Origin::signed(1), 0));
assert_eq!(last_event(), RawEvent::BountyClaimed(0, 0, 3)); assert_eq!(last_event(), BountiesEvent::BountyClaimed(0, 0, 3));
assert_eq!(Balances::free_balance(4), 70); // 30 + 50 - 10 assert_eq!(Balances::free_balance(4), 70); // 30 + 50 - 10
assert_eq!(Balances::free_balance(3), 0); assert_eq!(Balances::free_balance(3), 0);
@@ -806,7 +808,7 @@ fn award_and_cancel() {
assert_ok!(Bounties::unassign_curator(Origin::root(), 0)); assert_ok!(Bounties::unassign_curator(Origin::root(), 0));
assert_ok!(Bounties::close_bounty(Origin::root(), 0)); assert_ok!(Bounties::close_bounty(Origin::root(), 0));
assert_eq!(last_event(), RawEvent::BountyCanceled(0)); assert_eq!(last_event(), BountiesEvent::BountyCanceled(0));
assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 0); assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 0);
@@ -934,6 +936,48 @@ fn extend_expiry() {
}); });
} }
#[test]
fn test_migration_v4() {
let mut s = Storage::default();
let index: u32 = 10;
let bounty = Bounty::<u128, u64, u64> {
proposer: 0,
value: 20,
fee: 20,
curator_deposit: 20,
bond: 50,
status: BountyStatus::<u128, u64>::Proposed,
};
let data = vec![
(pallet_bounties::BountyCount::<Test>::hashed_key().to_vec(), 10.encode().to_vec()),
(pallet_bounties::Bounties::<Test>::hashed_key_for(index), bounty.encode().to_vec()),
(pallet_bounties::BountyDescriptions::<Test>::hashed_key_for(index), vec![0, 0]),
(
pallet_bounties::BountyApprovals::<Test>::hashed_key().to_vec(),
vec![10 as u32].encode().to_vec(),
),
];
s.top = data.into_iter().collect();
sp_io::TestExternalities::new(s).execute_with(|| {
use frame_support::traits::PalletInfo;
let old_pallet_name = <Test as frame_system::Config>::PalletInfo::name::<Bounties>()
.expect("Bounties is part of runtime, so it has a name; qed");
let new_pallet_name = "NewBounties";
crate::migrations::v4::pre_migration::<Test, Bounties, _>(old_pallet_name, new_pallet_name);
crate::migrations::v4::migrate::<Test, Bounties, _>(old_pallet_name, new_pallet_name);
crate::migrations::v4::post_migration::<Test, Bounties, _>(
old_pallet_name,
new_pallet_name,
);
});
}
#[test] #[test]
fn genesis_funding_works() { fn genesis_funding_works() {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap(); let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();