feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Treasury tips benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use frame_benchmarking::v1::{
|
||||
account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError,
|
||||
};
|
||||
use frame_support::ensure;
|
||||
use frame_system::RawOrigin;
|
||||
use sp_runtime::traits::Saturating;
|
||||
|
||||
use super::*;
|
||||
use crate::Pallet as TipsMod;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
// Create the pre-requisite information needed to create a `report_awesome`.
|
||||
fn setup_awesome<T: Config<I>, I: 'static>(length: u32) -> (T::AccountId, Vec<u8>, T::AccountId) {
|
||||
let caller = whitelisted_caller();
|
||||
let value = T::TipReportDepositBase::get() +
|
||||
T::DataDepositPerByte::get() * length.into() +
|
||||
T::Currency::minimum_balance();
|
||||
let _ = T::Currency::make_free_balance_be(&caller, value);
|
||||
let reason = vec![0; length as usize];
|
||||
let awesome_person = account("awesome", 0, SEED);
|
||||
(caller, reason, awesome_person)
|
||||
}
|
||||
|
||||
// Create the pre-requisite information needed to call `tip_new`.
|
||||
fn setup_tip<T: Config<I>, I: 'static>(
|
||||
r: u32,
|
||||
t: u32,
|
||||
) -> Result<(T::AccountId, Vec<u8>, T::AccountId, BalanceOf<T, I>), &'static str> {
|
||||
let tippers_count = T::Tippers::count();
|
||||
|
||||
for i in 0..t {
|
||||
let member = account("member", i, SEED);
|
||||
T::Tippers::add(&member);
|
||||
ensure!(T::Tippers::contains(&member), "failed to add tipper");
|
||||
}
|
||||
|
||||
ensure!(T::Tippers::count() == tippers_count + t as usize, "problem creating tippers");
|
||||
let caller = account("member", t - 1, SEED);
|
||||
let reason = vec![0; r as usize];
|
||||
let beneficiary = account("beneficiary", t, SEED);
|
||||
let value = T::Currency::minimum_balance().saturating_mul(100u32.into());
|
||||
Ok((caller, reason, beneficiary, value))
|
||||
}
|
||||
|
||||
// Create `t` new tips for the tip proposal with `hash`.
|
||||
// This function automatically makes the tip able to close.
|
||||
fn create_tips<T: Config<I>, I: 'static>(
|
||||
t: u32,
|
||||
hash: T::Hash,
|
||||
value: BalanceOf<T, I>,
|
||||
) -> Result<(), &'static str> {
|
||||
for i in 0..t {
|
||||
let caller = account("member", i, SEED);
|
||||
ensure!(T::Tippers::contains(&caller), "caller is not a tipper");
|
||||
TipsMod::<T, I>::tip(RawOrigin::Signed(caller).into(), hash, value)?;
|
||||
}
|
||||
Tips::<T, I>::mutate(hash, |maybe_tip| {
|
||||
if let Some(open_tip) = maybe_tip {
|
||||
open_tip.closes = Some(frame_system::pallet_prelude::BlockNumberFor::<T>::zero());
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_pot_account<T: Config<I>, I: 'static>() {
|
||||
let pot_account = TipsMod::<T, I>::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_pallet! {
|
||||
report_awesome {
|
||||
let r in 0 .. T::MaximumReasonLength::get();
|
||||
let (caller, reason, awesome_person) = setup_awesome::<T, I>(r);
|
||||
let awesome_person_lookup = T::Lookup::unlookup(awesome_person);
|
||||
// Whitelist caller account from further DB operations.
|
||||
let caller_key = frame_system::Account::<T>::hashed_key_for(&caller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into());
|
||||
}: _(RawOrigin::Signed(caller), reason, awesome_person_lookup)
|
||||
|
||||
retract_tip {
|
||||
let r = T::MaximumReasonLength::get();
|
||||
let (caller, reason, awesome_person) = setup_awesome::<T, I>(r);
|
||||
let awesome_person_lookup = T::Lookup::unlookup(awesome_person.clone());
|
||||
TipsMod::<T, I>::report_awesome(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
reason.clone(),
|
||||
awesome_person_lookup
|
||||
)?;
|
||||
let reason_hash = T::Hashing::hash(&reason[..]);
|
||||
let hash = T::Hashing::hash_of(&(&reason_hash, &awesome_person));
|
||||
// Whitelist caller account from further DB operations.
|
||||
let caller_key = frame_system::Account::<T>::hashed_key_for(&caller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into());
|
||||
}: _(RawOrigin::Signed(caller), hash)
|
||||
|
||||
tip_new {
|
||||
let r in 0 .. T::MaximumReasonLength::get();
|
||||
let t in 1 .. T::Tippers::max_len() as u32;
|
||||
|
||||
let (caller, reason, beneficiary, value) = setup_tip::<T, I>(r, t)?;
|
||||
let beneficiary_lookup = T::Lookup::unlookup(beneficiary);
|
||||
// Whitelist caller account from further DB operations.
|
||||
let caller_key = frame_system::Account::<T>::hashed_key_for(&caller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into());
|
||||
}: _(RawOrigin::Signed(caller), reason, beneficiary_lookup, value)
|
||||
|
||||
tip {
|
||||
let t in 1 .. T::Tippers::max_len() as u32;
|
||||
let (member, reason, beneficiary, value) = setup_tip::<T, I>(0, t)?;
|
||||
let beneficiary_lookup = T::Lookup::unlookup(beneficiary.clone());
|
||||
let value = T::Currency::minimum_balance().saturating_mul(100u32.into());
|
||||
TipsMod::<T, I>::tip_new(
|
||||
RawOrigin::Signed(member).into(),
|
||||
reason.clone(),
|
||||
beneficiary_lookup,
|
||||
value
|
||||
)?;
|
||||
let reason_hash = T::Hashing::hash(&reason[..]);
|
||||
let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary));
|
||||
ensure!(Tips::<T, I>::contains_key(hash), "tip does not exist");
|
||||
create_tips::<T, I>(t - 1, hash, value)?;
|
||||
let caller = account("member", t - 1, SEED);
|
||||
// Whitelist caller account from further DB operations.
|
||||
let caller_key = frame_system::Account::<T>::hashed_key_for(&caller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into());
|
||||
}: _(RawOrigin::Signed(caller), hash, value)
|
||||
|
||||
close_tip {
|
||||
let t in 1 .. T::Tippers::max_len() as u32;
|
||||
|
||||
// Make sure pot is funded
|
||||
setup_pot_account::<T, I>();
|
||||
|
||||
// Set up a new tip proposal
|
||||
let (member, reason, beneficiary, value) = setup_tip::<T, I>(0, t)?;
|
||||
let beneficiary_lookup = T::Lookup::unlookup(beneficiary.clone());
|
||||
let value = T::Currency::minimum_balance().saturating_mul(100u32.into());
|
||||
TipsMod::<T, I>::tip_new(
|
||||
RawOrigin::Signed(member).into(),
|
||||
reason.clone(),
|
||||
beneficiary_lookup,
|
||||
value
|
||||
)?;
|
||||
|
||||
// Create a bunch of tips
|
||||
let reason_hash = T::Hashing::hash(&reason[..]);
|
||||
let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary));
|
||||
ensure!(Tips::<T, I>::contains_key(hash), "tip does not exist");
|
||||
|
||||
create_tips::<T, I>(t, hash, value)?;
|
||||
|
||||
let caller = account("caller", t, SEED);
|
||||
// Whitelist caller account from further DB operations.
|
||||
let caller_key = frame_system::Account::<T>::hashed_key_for(&caller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into());
|
||||
}: _(RawOrigin::Signed(caller), hash)
|
||||
|
||||
slash_tip {
|
||||
let t in 1 .. T::Tippers::max_len() as u32;
|
||||
|
||||
// Make sure pot is funded
|
||||
setup_pot_account::<T, I>();
|
||||
|
||||
// Set up a new tip proposal
|
||||
let (member, reason, beneficiary, value) = setup_tip::<T, I>(0, t)?;
|
||||
let beneficiary_lookup = T::Lookup::unlookup(beneficiary.clone());
|
||||
let value = T::Currency::minimum_balance().saturating_mul(100u32.into());
|
||||
TipsMod::<T, I>::tip_new(
|
||||
RawOrigin::Signed(member).into(),
|
||||
reason.clone(),
|
||||
beneficiary_lookup,
|
||||
value
|
||||
)?;
|
||||
|
||||
let reason_hash = T::Hashing::hash(&reason[..]);
|
||||
let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary));
|
||||
ensure!(Tips::<T, I>::contains_key(hash), "tip does not exist");
|
||||
let reject_origin =
|
||||
T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
}: _<T::RuntimeOrigin>(reject_origin, hash)
|
||||
|
||||
impl_benchmark_test_suite!(TipsMod, crate::tests::new_test_ext(), crate::tests::Test);
|
||||
}
|
||||
@@ -0,0 +1,687 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! # Tipping Pallet ( pallet-tips )
|
||||
//!
|
||||
//! > NOTE: This pallet is tightly coupled with pallet-treasury.
|
||||
//!
|
||||
//! A subsystem to allow for an agile "tipping" process, whereby a reward may be given without first
|
||||
//! having a pre-determined stakeholder group come to consensus on how much should be paid.
|
||||
//!
|
||||
//! A group of `Tippers` is determined through the config `Config`. After half of these have
|
||||
//! declared some amount that they believe a particular reported reason deserves, then a countdown
|
||||
//! period is entered where any remaining members can declare their tip amounts also. After the
|
||||
//! close of the countdown period, the median of all declared tips is paid to the reported
|
||||
//! beneficiary, along with any finders fee, in case of a public (and bonded) original report.
|
||||
//!
|
||||
//!
|
||||
//! ### Terminology
|
||||
//!
|
||||
//! Tipping protocol:
|
||||
//! - **Tipping:** The process of gathering declarations of amounts to tip and taking the median
|
||||
//! amount to be transferred from the treasury to a beneficiary account.
|
||||
//! - **Tip Reason:** The reason for a tip; generally a URL which embodies or explains why a
|
||||
//! particular individual (identified by an account ID) is worthy of a recognition by the
|
||||
//! treasury.
|
||||
//! - **Finder:** The original public reporter of some reason for tipping.
|
||||
//! - **Finders Fee:** Some proportion of the tip amount that is paid to the reporter of the tip,
|
||||
//! rather than the main beneficiary.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Dispatchable Functions
|
||||
//!
|
||||
//! Tipping protocol:
|
||||
//! - `report_awesome` - Report something worthy of a tip and register for a finders fee.
|
||||
//! - `retract_tip` - Retract a previous (finders fee registered) report.
|
||||
//! - `tip_new` - Report an item worthy of a tip and declare a specific amount to tip.
|
||||
//! - `tip` - Declare or redeclare an amount to tip for a particular reason.
|
||||
//! - `close_tip` - Close and pay out a tip.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod benchmarking;
|
||||
mod tests;
|
||||
|
||||
pub mod migrations;
|
||||
pub mod weights;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use sp_runtime::{
|
||||
traits::{AccountIdConversion, BadOrigin, Hash, StaticLookup, TrailingZeroInput, Zero},
|
||||
Percent, RuntimeDebug,
|
||||
};
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{
|
||||
ContainsLengthBound, Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, Get,
|
||||
OnUnbalanced, ReservableCurrency, SortedMembers,
|
||||
},
|
||||
Parameter,
|
||||
};
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
|
||||
#[cfg(any(feature = "try-runtime", test))]
|
||||
use sp_runtime::TryRuntimeError;
|
||||
|
||||
pub use pallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
const LOG_TARGET: &str = "runtime::tips";
|
||||
|
||||
pub type BalanceOf<T, I = ()> = pallet_treasury::BalanceOf<T, I>;
|
||||
pub type NegativeImbalanceOf<T, I = ()> = pallet_treasury::NegativeImbalanceOf<T, I>;
|
||||
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
|
||||
|
||||
/// 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, scale_info::TypeInfo)]
|
||||
pub struct OpenTip<
|
||||
AccountId: Parameter,
|
||||
Balance: Parameter,
|
||||
BlockNumber: Parameter,
|
||||
Hash: Parameter,
|
||||
> {
|
||||
/// The hash of the reason for the tip. The reason should be a human-readable UTF-8 encoded
|
||||
/// string. A URL would be sensible.
|
||||
reason: Hash,
|
||||
/// The account to be tipped.
|
||||
who: AccountId,
|
||||
/// The account who began this tip.
|
||||
finder: AccountId,
|
||||
/// The amount held on deposit for this tip.
|
||||
deposit: Balance,
|
||||
/// The block number at which this tip will close if `Some`. If `None`, then no closing is
|
||||
/// scheduled.
|
||||
closes: Option<BlockNumber>,
|
||||
/// The members who have voted for this tip. Sorted by AccountId.
|
||||
tips: Vec<(AccountId, Balance)>,
|
||||
/// Whether this tip should result in the finder taking a fee.
|
||||
finders_fee: bool,
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
/// The in-code storage version.
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::storage_version(STORAGE_VERSION)]
|
||||
#[pallet::without_storage_info]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config<I: 'static = ()>: frame_system::Config + pallet_treasury::Config<I> {
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Maximum acceptable reason length.
|
||||
///
|
||||
/// Benchmarks depend on this value, be sure to update weights file when changing this value
|
||||
#[pallet::constant]
|
||||
type MaximumReasonLength: Get<u32>;
|
||||
|
||||
/// The amount held on deposit per byte within the tip report reason or bounty description.
|
||||
#[pallet::constant]
|
||||
type DataDepositPerByte: Get<BalanceOf<Self, I>>;
|
||||
|
||||
/// The period for which a tip remains open after is has achieved threshold tippers.
|
||||
#[pallet::constant]
|
||||
type TipCountdown: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The percent of the final tip which goes to the original reporter of the tip.
|
||||
#[pallet::constant]
|
||||
type TipFindersFee: Get<Percent>;
|
||||
|
||||
/// The non-zero amount held on deposit for placing a tip report.
|
||||
#[pallet::constant]
|
||||
type TipReportDepositBase: Get<BalanceOf<Self, I>>;
|
||||
|
||||
/// The maximum amount for a single tip.
|
||||
#[pallet::constant]
|
||||
type MaxTipAmount: Get<BalanceOf<Self, I>>;
|
||||
|
||||
/// Origin from which tippers must come.
|
||||
///
|
||||
/// `ContainsLengthBound::max_len` must be cost free (i.e. no storage read or heavy
|
||||
/// operation). Benchmarks depend on the value of `ContainsLengthBound::max_len` be sure to
|
||||
/// update weights file when altering this method.
|
||||
type Tippers: SortedMembers<Self::AccountId> + ContainsLengthBound;
|
||||
|
||||
/// Handler for the unbalanced decrease when slashing for a removed tip.
|
||||
type OnSlash: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[pallet::storage]
|
||||
pub type Tips<T: Config<I>, I: 'static = ()> = StorageMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::Hash,
|
||||
OpenTip<T::AccountId, BalanceOf<T, I>, BlockNumberFor<T>, T::Hash>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// 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.
|
||||
#[pallet::storage]
|
||||
pub type Reasons<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Identity, T::Hash, Vec<u8>, OptionQuery>;
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// A new tip suggestion has been opened.
|
||||
NewTip { tip_hash: T::Hash },
|
||||
/// A tip suggestion has reached threshold and is closing.
|
||||
TipClosing { tip_hash: T::Hash },
|
||||
/// A tip suggestion has been closed.
|
||||
TipClosed { tip_hash: T::Hash, who: T::AccountId, payout: BalanceOf<T, I> },
|
||||
/// A tip suggestion has been retracted.
|
||||
TipRetracted { tip_hash: T::Hash },
|
||||
/// A tip suggestion has been slashed.
|
||||
TipSlashed { tip_hash: T::Hash, finder: T::AccountId, deposit: BalanceOf<T, I> },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// The reason given is just too big.
|
||||
ReasonTooBig,
|
||||
/// The tip was already found/started.
|
||||
AlreadyKnown,
|
||||
/// The tip hash is unknown.
|
||||
UnknownTip,
|
||||
/// The tip given was too generous.
|
||||
MaxTipAmountExceeded,
|
||||
/// 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::call]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Report something `reason` that deserves a tip and claim any eventual the finder's fee.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// Payment: `TipReportDepositBase` will be reserved from the origin account, as well as
|
||||
/// `DataDepositPerByte` for each byte in `reason`.
|
||||
///
|
||||
/// - `reason`: The reason for, or the thing that deserves, the tip; generally this will be
|
||||
/// a UTF-8-encoded URL.
|
||||
/// - `who`: The account which should be credited for the tip.
|
||||
///
|
||||
/// Emits `NewTip` if successful.
|
||||
///
|
||||
/// ## Complexity
|
||||
/// - `O(R)` where `R` length of `reason`.
|
||||
/// - encoding and hashing of 'reason'
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::report_awesome(reason.len() as u32))]
|
||||
pub fn report_awesome(
|
||||
origin: OriginFor<T>,
|
||||
reason: Vec<u8>,
|
||||
who: AccountIdLookupOf<T>,
|
||||
) -> DispatchResult {
|
||||
let finder = ensure_signed(origin)?;
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
|
||||
ensure!(
|
||||
reason.len() <= T::MaximumReasonLength::get() as usize,
|
||||
Error::<T, I>::ReasonTooBig
|
||||
);
|
||||
|
||||
let reason_hash = T::Hashing::hash(&reason[..]);
|
||||
ensure!(!Reasons::<T, I>::contains_key(&reason_hash), Error::<T, I>::AlreadyKnown);
|
||||
let hash = T::Hashing::hash_of(&(&reason_hash, &who));
|
||||
ensure!(!Tips::<T, I>::contains_key(&hash), Error::<T, I>::AlreadyKnown);
|
||||
|
||||
let deposit = T::TipReportDepositBase::get() +
|
||||
T::DataDepositPerByte::get() * (reason.len() as u32).into();
|
||||
T::Currency::reserve(&finder, deposit)?;
|
||||
|
||||
Reasons::<T, I>::insert(&reason_hash, &reason);
|
||||
let tip = OpenTip {
|
||||
reason: reason_hash,
|
||||
who,
|
||||
finder,
|
||||
deposit,
|
||||
closes: None,
|
||||
tips: vec![],
|
||||
finders_fee: true,
|
||||
};
|
||||
Tips::<T, I>::insert(&hash, tip);
|
||||
Self::deposit_event(Event::NewTip { tip_hash: hash });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retract a prior tip-report from `report_awesome`, and cancel the process of tipping.
|
||||
///
|
||||
/// If successful, the original deposit will be unreserved.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ and the tip identified by `hash`
|
||||
/// must have been reported by the signing account through `report_awesome` (and not
|
||||
/// through `tip_new`).
|
||||
///
|
||||
/// - `hash`: The identity of the open tip for which a tip value is declared. This is formed
|
||||
/// as the hash of the tuple of the original tip `reason` and the beneficiary account ID.
|
||||
///
|
||||
/// Emits `TipRetracted` if successful.
|
||||
///
|
||||
/// ## Complexity
|
||||
/// - `O(1)`
|
||||
/// - Depends on the length of `T::Hash` which is fixed.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::retract_tip())]
|
||||
pub fn retract_tip(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
let tip = Tips::<T, I>::get(&hash).ok_or(Error::<T, I>::UnknownTip)?;
|
||||
ensure!(tip.finder == who, Error::<T, I>::NotFinder);
|
||||
|
||||
Reasons::<T, I>::remove(&tip.reason);
|
||||
Tips::<T, I>::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(Event::TipRetracted { tip_hash: hash });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Give a tip for something new; no finder's fee will be taken.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ and the signing account must be a
|
||||
/// member of the `Tippers` set.
|
||||
///
|
||||
/// - `reason`: The reason for, or the thing that deserves, the tip; generally this will be
|
||||
/// a UTF-8-encoded URL.
|
||||
/// - `who`: The account which should be credited for the tip.
|
||||
/// - `tip_value`: The amount of tip that the sender would like to give. The median tip
|
||||
/// value of active tippers will be given to the `who`.
|
||||
///
|
||||
/// Emits `NewTip` if successful.
|
||||
///
|
||||
/// ## 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`
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))]
|
||||
pub fn tip_new(
|
||||
origin: OriginFor<T>,
|
||||
reason: Vec<u8>,
|
||||
who: AccountIdLookupOf<T>,
|
||||
#[pallet::compact] tip_value: BalanceOf<T, I>,
|
||||
) -> DispatchResult {
|
||||
let tipper = ensure_signed(origin)?;
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
ensure!(T::Tippers::contains(&tipper), BadOrigin);
|
||||
|
||||
ensure!(T::MaxTipAmount::get() >= tip_value, Error::<T, I>::MaxTipAmountExceeded);
|
||||
|
||||
let reason_hash = T::Hashing::hash(&reason[..]);
|
||||
ensure!(!Reasons::<T, I>::contains_key(&reason_hash), Error::<T, I>::AlreadyKnown);
|
||||
|
||||
let hash = T::Hashing::hash_of(&(&reason_hash, &who));
|
||||
Reasons::<T, I>::insert(&reason_hash, &reason);
|
||||
Self::deposit_event(Event::NewTip { tip_hash: hash });
|
||||
let tips = vec![(tipper.clone(), tip_value)];
|
||||
let tip = OpenTip {
|
||||
reason: reason_hash,
|
||||
who,
|
||||
finder: tipper,
|
||||
deposit: Zero::zero(),
|
||||
closes: None,
|
||||
tips,
|
||||
finders_fee: false,
|
||||
};
|
||||
Tips::<T, I>::insert(&hash, tip);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Declare a tip value for an already-open tip.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ and the signing account must be a
|
||||
/// member of the `Tippers` set.
|
||||
///
|
||||
/// - `hash`: The identity of the open tip for which a tip value is declared. This is formed
|
||||
/// as the hash of the tuple of the hash of the original tip `reason` and the beneficiary
|
||||
/// account ID.
|
||||
/// - `tip_value`: The amount of tip that the sender would like to give. The median tip
|
||||
/// value of active tippers will be given to the `who`.
|
||||
///
|
||||
/// Emits `TipClosing` if the threshold of tippers has been reached and the countdown period
|
||||
/// has started.
|
||||
///
|
||||
/// ## 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`.
|
||||
#[pallet::call_index(3)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::tip(T::Tippers::max_len() as u32))]
|
||||
pub fn tip(
|
||||
origin: OriginFor<T>,
|
||||
hash: T::Hash,
|
||||
#[pallet::compact] tip_value: BalanceOf<T, I>,
|
||||
) -> DispatchResult {
|
||||
let tipper = ensure_signed(origin)?;
|
||||
ensure!(T::Tippers::contains(&tipper), BadOrigin);
|
||||
|
||||
ensure!(T::MaxTipAmount::get() >= tip_value, Error::<T, I>::MaxTipAmountExceeded);
|
||||
|
||||
let mut tip = Tips::<T, I>::get(hash).ok_or(Error::<T, I>::UnknownTip)?;
|
||||
|
||||
if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) {
|
||||
Self::deposit_event(Event::TipClosing { tip_hash: hash });
|
||||
}
|
||||
Tips::<T, I>::insert(&hash, tip);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Close and payout a tip.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// The tip identified by `hash` must have finished its countdown period.
|
||||
///
|
||||
/// - `hash`: The identity of the open tip for which a tip value is declared. This is formed
|
||||
/// as the hash of the tuple of the original tip `reason` and the beneficiary account ID.
|
||||
///
|
||||
/// ## 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`.
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::close_tip(T::Tippers::max_len() as u32))]
|
||||
pub fn close_tip(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
|
||||
ensure_signed(origin)?;
|
||||
|
||||
let tip = Tips::<T, I>::get(hash).ok_or(Error::<T, I>::UnknownTip)?;
|
||||
let n = tip.closes.as_ref().ok_or(Error::<T, I>::StillOpen)?;
|
||||
ensure!(frame_system::Pallet::<T>::block_number() >= *n, Error::<T, I>::Premature);
|
||||
// closed.
|
||||
Reasons::<T, I>::remove(&tip.reason);
|
||||
Tips::<T, I>::remove(hash);
|
||||
Self::payout_tip(hash, tip);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove and slash an already-open tip.
|
||||
///
|
||||
/// May only be called from `T::RejectOrigin`.
|
||||
///
|
||||
/// As a result, the finder is slashed and the deposits are lost.
|
||||
///
|
||||
/// Emits `TipSlashed` if successful.
|
||||
///
|
||||
/// ## Complexity
|
||||
/// - O(1).
|
||||
#[pallet::call_index(5)]
|
||||
#[pallet::weight(<T as Config<I>>::WeightInfo::slash_tip(T::Tippers::max_len() as u32))]
|
||||
pub fn slash_tip(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
|
||||
T::RejectOrigin::ensure_origin(origin)?;
|
||||
|
||||
let tip = Tips::<T, I>::take(hash).ok_or(Error::<T, I>::UnknownTip)?;
|
||||
|
||||
if !tip.deposit.is_zero() {
|
||||
let imbalance = T::Currency::slash_reserved(&tip.finder, tip.deposit).0;
|
||||
T::OnSlash::on_unbalanced(imbalance);
|
||||
}
|
||||
Reasons::<T, I>::remove(&tip.reason);
|
||||
Self::deposit_event(Event::TipSlashed {
|
||||
tip_hash: hash,
|
||||
finder: tip.finder,
|
||||
deposit: tip.deposit,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
|
||||
fn integrity_test() {
|
||||
assert!(
|
||||
!T::TipReportDepositBase::get().is_zero(),
|
||||
"`TipReportDepositBase` should not be zero",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
|
||||
Self::do_try_state()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
// Add public immutables and private mutables.
|
||||
|
||||
/// Access tips storage from outside
|
||||
pub fn tips(
|
||||
hash: T::Hash,
|
||||
) -> Option<OpenTip<T::AccountId, BalanceOf<T, I>, BlockNumberFor<T>, T::Hash>> {
|
||||
Tips::<T, I>::get(hash)
|
||||
}
|
||||
|
||||
/// Access reasons storage from outside
|
||||
pub fn reasons(hash: T::Hash) -> Option<Vec<u8>> {
|
||||
Reasons::<T, I>::get(hash)
|
||||
}
|
||||
|
||||
/// The account ID of the treasury pot.
|
||||
///
|
||||
/// This actually does computation. If you need to keep using it, then make sure you cache the
|
||||
/// value and only call this once.
|
||||
pub fn account_id() -> T::AccountId {
|
||||
T::PalletId::get().into_account_truncating()
|
||||
}
|
||||
|
||||
/// Given a mutable reference to an `OpenTip`, insert the tip into it and check whether it
|
||||
/// closes, if so, then deposit the relevant event and set closing accordingly.
|
||||
///
|
||||
/// `O(T)` and one storage access.
|
||||
fn insert_tip_and_check_closing(
|
||||
tip: &mut OpenTip<T::AccountId, BalanceOf<T, I>, BlockNumberFor<T>, T::Hash>,
|
||||
tipper: T::AccountId,
|
||||
tip_value: BalanceOf<T, I>,
|
||||
) -> bool {
|
||||
match tip.tips.binary_search_by_key(&&tipper, |x| &x.0) {
|
||||
Ok(pos) => tip.tips[pos] = (tipper, tip_value),
|
||||
Err(pos) => tip.tips.insert(pos, (tipper, tip_value)),
|
||||
}
|
||||
Self::retain_active_tips(&mut tip.tips);
|
||||
let threshold = T::Tippers::count().div_ceil(2);
|
||||
if tip.tips.len() >= threshold && tip.closes.is_none() {
|
||||
tip.closes = Some(frame_system::Pallet::<T>::block_number() + T::TipCountdown::get());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove any non-members of `Tippers` from a `tips` vector. `O(T)`.
|
||||
fn retain_active_tips(tips: &mut Vec<(T::AccountId, BalanceOf<T, I>)>) {
|
||||
let members = T::Tippers::sorted_members();
|
||||
let mut members_iter = members.iter();
|
||||
let mut member = members_iter.next();
|
||||
tips.retain(|(ref a, _)| loop {
|
||||
match member {
|
||||
None => break false,
|
||||
Some(m) if m > a => break false,
|
||||
Some(m) => {
|
||||
member = members_iter.next();
|
||||
if m < a {
|
||||
continue;
|
||||
} else {
|
||||
break true;
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Execute the payout of a tip.
|
||||
///
|
||||
/// Up to three balance operations.
|
||||
/// Plus `O(T)` (`T` is Tippers length).
|
||||
fn payout_tip(
|
||||
hash: T::Hash,
|
||||
tip: OpenTip<T::AccountId, BalanceOf<T, I>, BlockNumberFor<T>, T::Hash>,
|
||||
) {
|
||||
let mut tips = tip.tips;
|
||||
Self::retain_active_tips(&mut tips);
|
||||
tips.sort_by_key(|i| i.1);
|
||||
|
||||
let treasury = Self::account_id();
|
||||
let max_payout = pallet_treasury::Pallet::<T, I>::pot();
|
||||
|
||||
let mut payout = tips[tips.len() / 2].1.min(max_payout);
|
||||
if !tip.deposit.is_zero() {
|
||||
let err_amount = T::Currency::unreserve(&tip.finder, tip.deposit);
|
||||
debug_assert!(err_amount.is_zero());
|
||||
}
|
||||
|
||||
if tip.finders_fee && tip.finder != tip.who {
|
||||
// pay out the finder's fee.
|
||||
let finders_fee = T::TipFindersFee::get() * payout;
|
||||
payout -= finders_fee;
|
||||
// this should go through given we checked it's at most the free balance, but still
|
||||
// we only make a best-effort.
|
||||
let res = T::Currency::transfer(&treasury, &tip.finder, finders_fee, KeepAlive);
|
||||
debug_assert!(res.is_ok());
|
||||
}
|
||||
|
||||
// same as above: best-effort only.
|
||||
let res = T::Currency::transfer(&treasury, &tip.who, payout, KeepAlive);
|
||||
debug_assert!(res.is_ok());
|
||||
Self::deposit_event(Event::TipClosed { tip_hash: hash, who: tip.who, payout });
|
||||
}
|
||||
|
||||
pub fn migrate_retract_tip_for_tip_new(module: &[u8], item: &[u8]) {
|
||||
/// An open tipping "motion". Retains all details of a tip including information on the
|
||||
/// finder and the members who have voted.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)]
|
||||
pub struct OldOpenTip<
|
||||
AccountId: Parameter,
|
||||
Balance: Parameter,
|
||||
BlockNumber: Parameter,
|
||||
Hash: Parameter,
|
||||
> {
|
||||
/// The hash of the reason for the tip. The reason should be a human-readable UTF-8
|
||||
/// encoded string. A URL would be sensible.
|
||||
reason: Hash,
|
||||
/// The account to be tipped.
|
||||
who: AccountId,
|
||||
/// The account who began this tip and the amount held on deposit.
|
||||
finder: Option<(AccountId, Balance)>,
|
||||
/// The block number at which this tip will close if `Some`. If `None`, then no closing
|
||||
/// is scheduled.
|
||||
closes: Option<BlockNumber>,
|
||||
/// The members who have voted for this tip. Sorted by AccountId.
|
||||
tips: Vec<(AccountId, Balance)>,
|
||||
}
|
||||
|
||||
use frame_support::{migration::storage_key_iter, Twox64Concat};
|
||||
|
||||
let zero_account = T::AccountId::decode(&mut TrailingZeroInput::new(&[][..]))
|
||||
.expect("infinite input; qed");
|
||||
|
||||
for (hash, old_tip) in storage_key_iter::<
|
||||
T::Hash,
|
||||
OldOpenTip<T::AccountId, BalanceOf<T, I>, BlockNumberFor<T>, T::Hash>,
|
||||
Twox64Concat,
|
||||
>(module, item)
|
||||
.drain()
|
||||
{
|
||||
let (finder, deposit, finders_fee) = match old_tip.finder {
|
||||
Some((finder, deposit)) => (finder, deposit, true),
|
||||
None => (zero_account.clone(), Zero::zero(), false),
|
||||
};
|
||||
let new_tip = OpenTip {
|
||||
reason: old_tip.reason,
|
||||
who: old_tip.who,
|
||||
finder,
|
||||
deposit,
|
||||
closes: old_tip.closes,
|
||||
tips: old_tip.tips,
|
||||
finders_fee,
|
||||
};
|
||||
Tips::<T, I>::insert(hash, new_tip)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the correctness of the state of this pallet.
|
||||
///
|
||||
/// This should be valid before and after each state transition of this pallet.
|
||||
///
|
||||
/// ## Invariants:
|
||||
/// 1. The number of entries in `Tips` should be equal to `Reasons`.
|
||||
/// 2. Reasons exists for each Tip[`OpenTip.reason`].
|
||||
/// 3. If `OpenTip.finders_fee` is true, then OpenTip.deposit should be greater than zero.
|
||||
#[cfg(any(feature = "try-runtime", test))]
|
||||
pub fn do_try_state() -> Result<(), TryRuntimeError> {
|
||||
let reasons = Reasons::<T, I>::iter_keys().collect::<Vec<_>>();
|
||||
let tips = Tips::<T, I>::iter_keys().collect::<Vec<_>>();
|
||||
|
||||
ensure!(
|
||||
reasons.len() == tips.len(),
|
||||
TryRuntimeError::Other("Equal length of entries in `Tips` and `Reasons` Storage")
|
||||
);
|
||||
|
||||
for tip in Tips::<T, I>::iter_keys() {
|
||||
let open_tip = Tips::<T, I>::get(&tip).expect("All map keys are valid; qed");
|
||||
|
||||
if open_tip.finders_fee {
|
||||
ensure!(
|
||||
!open_tip.deposit.is_zero(),
|
||||
TryRuntimeError::Other(
|
||||
"Tips with `finders_fee` should have non-zero `deposit`."
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ensure!(
|
||||
reasons.contains(&open_tip.reason),
|
||||
TryRuntimeError::Other("no reason for this tip")
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
///
|
||||
/// For backward compatibility reasons, pallet-tips uses `Treasury` for storage module prefix
|
||||
/// before calling this migration. After calling this migration, it will get replaced with
|
||||
/// own storage identifier.
|
||||
pub mod v4;
|
||||
|
||||
/// A migration that unreserves all funds held in the context of this pallet.
|
||||
pub mod unreserve_deposits;
|
||||
@@ -0,0 +1,324 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! A migration that unreserves all deposit and unlocks all stake held in the context of this
|
||||
//! pallet.
|
||||
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use core::iter::Sum;
|
||||
use frame_support::{
|
||||
pallet_prelude::OptionQuery,
|
||||
storage_alias,
|
||||
traits::{Currency, LockableCurrency, OnRuntimeUpgrade, ReservableCurrency},
|
||||
weights::RuntimeDbWeight,
|
||||
Parameter, Twox64Concat,
|
||||
};
|
||||
use sp_runtime::{traits::Zero, Saturating};
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
const LOG_TARGET: &str = "runtime::tips::migrations::unreserve_deposits";
|
||||
|
||||
type BalanceOf<T, I> =
|
||||
<<T as UnlockConfig<I>>::Currency as Currency<<T as UnlockConfig<I>>::AccountId>>::Balance;
|
||||
|
||||
/// The configuration for [`UnreserveDeposits`].
|
||||
pub trait UnlockConfig<I>: 'static {
|
||||
/// The hash used in the runtime.
|
||||
type Hash: Parameter;
|
||||
/// The account ID used in the runtime.
|
||||
type AccountId: Parameter + Ord;
|
||||
/// The currency type used in the runtime.
|
||||
///
|
||||
/// Should match the currency type previously used for the pallet, if applicable.
|
||||
type Currency: LockableCurrency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
|
||||
/// Base deposit to report a tip.
|
||||
///
|
||||
/// Should match the currency type previously used for the pallet, if applicable.
|
||||
type TipReportDepositBase: sp_core::Get<BalanceOf<Self, I>>;
|
||||
/// Deposit per byte to report a tip.
|
||||
///
|
||||
/// Should match the currency type previously used for the pallet, if applicable.
|
||||
type DataDepositPerByte: sp_core::Get<BalanceOf<Self, I>>;
|
||||
/// The name of the pallet as previously configured in
|
||||
/// [`construct_runtime!`](frame_support::construct_runtime).
|
||||
type PalletName: sp_core::Get<&'static str>;
|
||||
/// The DB weight as configured in the runtime to calculate the correct weight.
|
||||
type DbWeight: sp_core::Get<RuntimeDbWeight>;
|
||||
/// The block number as configured in the runtime.
|
||||
type BlockNumber: Parameter + Zero + Copy + Ord;
|
||||
}
|
||||
|
||||
/// An open tipping "motion". Retains all details of a tip including information on the finder
|
||||
/// and the members who have voted.
|
||||
#[storage_alias(dynamic)]
|
||||
type Tips<T: UnlockConfig<I>, I: 'static> = StorageMap<
|
||||
<T as UnlockConfig<I>>::PalletName,
|
||||
Twox64Concat,
|
||||
<T as UnlockConfig<I>>::Hash,
|
||||
crate::OpenTip<
|
||||
<T as UnlockConfig<I>>::AccountId,
|
||||
BalanceOf<T, I>,
|
||||
<T as UnlockConfig<I>>::BlockNumber,
|
||||
<T as UnlockConfig<I>>::Hash,
|
||||
>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// A migration that unreserves all tip deposits.
|
||||
///
|
||||
/// Useful to prevent funds from being locked up when the pallet is deprecated.
|
||||
///
|
||||
/// The pallet should be made inoperable before or immediately after this migration is run.
|
||||
///
|
||||
/// (See also the `RemovePallet` migration in `frame/support/src/migrations.rs`)
|
||||
pub struct UnreserveDeposits<T: UnlockConfig<I>, I: 'static>(core::marker::PhantomData<(T, I)>);
|
||||
|
||||
impl<T: UnlockConfig<I>, I: 'static> UnreserveDeposits<T, I> {
|
||||
/// Calculates and returns the total amount reserved by each account by this pallet from open
|
||||
/// tips.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `BTreeMap<T::AccountId, T::Balance>`: Map of account IDs to their respective total
|
||||
/// reserved balance by this pallet
|
||||
/// * `frame_support::weights::Weight`: The weight of this operation.
|
||||
fn get_deposits() -> (BTreeMap<T::AccountId, BalanceOf<T, I>>, frame_support::weights::Weight) {
|
||||
use sp_core::Get;
|
||||
|
||||
let mut tips_len = 0;
|
||||
let account_deposits: BTreeMap<T::AccountId, BalanceOf<T, I>> = Tips::<T, I>::iter()
|
||||
.map(|(_hash, open_tip)| open_tip)
|
||||
.fold(BTreeMap::new(), |mut acc, tip| {
|
||||
// Count the total number of tips
|
||||
tips_len.saturating_inc();
|
||||
|
||||
// Add the balance to the account's existing deposit in the accumulator
|
||||
acc.entry(tip.finder).or_insert(Zero::zero()).saturating_accrue(tip.deposit);
|
||||
acc
|
||||
});
|
||||
|
||||
(account_deposits, T::DbWeight::get().reads(tips_len))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UnlockConfig<I>, I: 'static> OnRuntimeUpgrade for UnreserveDeposits<T, I>
|
||||
where
|
||||
BalanceOf<T, I>: Sum,
|
||||
{
|
||||
/// Gets the actual reserved amount for each account before the migration, performs integrity
|
||||
/// checks and prints some summary information.
|
||||
///
|
||||
/// Steps:
|
||||
/// 1. Gets the deposited balances for each account stored in this pallet.
|
||||
/// 2. Collects actual pre-migration reserved balances for each account.
|
||||
/// 3. Checks the integrity of the deposited balances.
|
||||
/// 4. Prints summary statistics about the state to be migrated.
|
||||
/// 5. Returns the pre-migration actual reserved balance for each account that will
|
||||
/// be part of the migration.
|
||||
///
|
||||
/// Fails with a `TryRuntimeError` if somehow the amount reserved by this pallet is greater than
|
||||
/// the actual total reserved amount for any accounts.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
|
||||
use codec::Encode;
|
||||
use frame_support::ensure;
|
||||
|
||||
// Get the Tips pallet view of balances it has reserved
|
||||
let (account_deposits, _) = Self::get_deposits();
|
||||
|
||||
// Get the actual amounts reserved for accounts with open tips
|
||||
let account_reserved_before: BTreeMap<T::AccountId, BalanceOf<T, I>> = account_deposits
|
||||
.keys()
|
||||
.map(|account| (account.clone(), T::Currency::reserved_balance(&account)))
|
||||
.collect();
|
||||
|
||||
// The deposit amount must be less than or equal to the reserved amount.
|
||||
// If it is higher, there is either a bug with the pallet or a bug in the calculation of the
|
||||
// deposit amount.
|
||||
ensure!(
|
||||
account_deposits.iter().all(|(account, deposit)| *deposit <=
|
||||
*account_reserved_before.get(account).unwrap_or(&Zero::zero())),
|
||||
"Deposit amount is greater than reserved amount"
|
||||
);
|
||||
|
||||
// Print some summary stats
|
||||
let total_deposits_to_unreserve =
|
||||
account_deposits.clone().into_values().sum::<BalanceOf<T, I>>();
|
||||
log::info!(target: LOG_TARGET, "Total accounts: {}", account_deposits.keys().count());
|
||||
log::info!(target: LOG_TARGET, "Total amount to unreserve: {:?}", total_deposits_to_unreserve);
|
||||
|
||||
// Return the actual amount reserved before the upgrade to verify integrity of the upgrade
|
||||
// in the post_upgrade hook.
|
||||
Ok(account_reserved_before.encode())
|
||||
}
|
||||
|
||||
/// Executes the migration, unreserving funds that are locked in Tip deposits.
|
||||
fn on_runtime_upgrade() -> frame_support::weights::Weight {
|
||||
use frame_support::traits::Get;
|
||||
|
||||
// Get staked and deposited balances as reported by this pallet.
|
||||
let (account_deposits, initial_reads) = Self::get_deposits();
|
||||
|
||||
// Deposited funds need to be unreserved.
|
||||
for (account, unreserve_amount) in account_deposits.iter() {
|
||||
if unreserve_amount.is_zero() {
|
||||
continue;
|
||||
}
|
||||
T::Currency::unreserve(&account, *unreserve_amount);
|
||||
}
|
||||
|
||||
T::DbWeight::get()
|
||||
.reads_writes(account_deposits.len() as u64, account_deposits.len() as u64)
|
||||
.saturating_add(initial_reads)
|
||||
}
|
||||
|
||||
/// Verifies that the account reserved balances were reduced by the actual expected amounts.
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(
|
||||
account_reserved_before_bytes: alloc::vec::Vec<u8>,
|
||||
) -> Result<(), sp_runtime::TryRuntimeError> {
|
||||
use codec::Decode;
|
||||
|
||||
let account_reserved_before = BTreeMap::<T::AccountId, BalanceOf<T, I>>::decode(
|
||||
&mut &account_reserved_before_bytes[..],
|
||||
)
|
||||
.map_err(|_| "Failed to decode account_reserved_before_bytes")?;
|
||||
|
||||
// Get deposited balances as reported by this pallet.
|
||||
let (account_deposits, _) = Self::get_deposits();
|
||||
|
||||
// Check that the reserved balance is reduced by the expected deposited amount.
|
||||
for (account, actual_reserved_before) in account_reserved_before {
|
||||
let actual_reserved_after = T::Currency::reserved_balance(&account);
|
||||
let expected_amount_deducted = *account_deposits
|
||||
.get(&account)
|
||||
.expect("account deposit must exist to be in account_reserved_before, qed");
|
||||
let expected_reserved_after =
|
||||
actual_reserved_before.saturating_sub(expected_amount_deducted);
|
||||
|
||||
if actual_reserved_after != expected_reserved_after {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Reserved balance for {:?} is incorrect. actual before: {:?}, actual after, {:?}, expected deducted: {:?}",
|
||||
account,
|
||||
actual_reserved_before,
|
||||
actual_reserved_after,
|
||||
expected_amount_deducted
|
||||
);
|
||||
return Err("Reserved balance is incorrect".into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "try-runtime", test))]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
migrations::unreserve_deposits::UnreserveDeposits,
|
||||
tests::{new_test_ext, Balances, RuntimeOrigin, Test, Tips},
|
||||
};
|
||||
use frame_support::{assert_ok, parameter_types, traits::TypedGet};
|
||||
use frame_system::pallet_prelude::BlockNumberFor;
|
||||
use sp_core::ConstU64;
|
||||
|
||||
parameter_types! {
|
||||
const PalletName: &'static str = "Tips";
|
||||
}
|
||||
|
||||
struct UnlockConfigImpl;
|
||||
impl super::UnlockConfig<()> for UnlockConfigImpl {
|
||||
type Currency = Balances;
|
||||
type TipReportDepositBase = ConstU64<1>;
|
||||
type DataDepositPerByte = ConstU64<1>;
|
||||
type Hash = sp_core::H256;
|
||||
type AccountId = u128;
|
||||
type BlockNumber = BlockNumberFor<Test>;
|
||||
type DbWeight = ();
|
||||
type PalletName = PalletName;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unreserve_all_funds_works() {
|
||||
let tipper_0 = 0;
|
||||
let tipper_1 = 1;
|
||||
let tipper_0_initial_reserved = 0;
|
||||
let tipper_1_initial_reserved = 5;
|
||||
let recipient = 100;
|
||||
let tip_0_reason = b"what_is_really_not_awesome".to_vec();
|
||||
let tip_1_reason = b"pineapple_on_pizza".to_vec();
|
||||
new_test_ext().execute_with(|| {
|
||||
// Set up
|
||||
assert_ok!(<Test as pallet_treasury::Config>::Currency::reserve(
|
||||
&tipper_0,
|
||||
tipper_0_initial_reserved
|
||||
));
|
||||
assert_ok!(<Test as pallet_treasury::Config>::Currency::reserve(
|
||||
&tipper_1,
|
||||
tipper_1_initial_reserved
|
||||
));
|
||||
|
||||
// Make some tips
|
||||
assert_ok!(Tips::report_awesome(
|
||||
RuntimeOrigin::signed(tipper_0),
|
||||
tip_0_reason.clone(),
|
||||
recipient
|
||||
));
|
||||
assert_ok!(Tips::report_awesome(
|
||||
RuntimeOrigin::signed(tipper_1),
|
||||
tip_1_reason.clone(),
|
||||
recipient
|
||||
));
|
||||
|
||||
// Verify the expected amount is reserved
|
||||
assert_eq!(
|
||||
<Test as pallet_treasury::Config>::Currency::reserved_balance(&tipper_0),
|
||||
tipper_0_initial_reserved +
|
||||
<Test as crate::Config>::TipReportDepositBase::get() +
|
||||
<Test as crate::Config>::DataDepositPerByte::get() *
|
||||
tip_0_reason.len() as u64
|
||||
);
|
||||
assert_eq!(
|
||||
<Test as pallet_treasury::Config>::Currency::reserved_balance(&tipper_1),
|
||||
tipper_1_initial_reserved +
|
||||
<Test as crate::Config>::TipReportDepositBase::get() +
|
||||
<Test as crate::Config>::DataDepositPerByte::get() *
|
||||
tip_1_reason.len() as u64
|
||||
);
|
||||
|
||||
// Execute the migration
|
||||
let bytes = match UnreserveDeposits::<UnlockConfigImpl, ()>::pre_upgrade() {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => panic!("pre_upgrade failed: {:?}", e),
|
||||
};
|
||||
UnreserveDeposits::<UnlockConfigImpl, ()>::on_runtime_upgrade();
|
||||
assert_ok!(UnreserveDeposits::<UnlockConfigImpl, ()>::post_upgrade(bytes));
|
||||
|
||||
// Check the deposits were were unreserved
|
||||
assert_eq!(
|
||||
<Test as pallet_treasury::Config>::Currency::reserved_balance(&tipper_0),
|
||||
tipper_0_initial_reserved
|
||||
);
|
||||
assert_eq!(
|
||||
<Test as pallet_treasury::Config>::Currency::reserved_balance(&tipper_1),
|
||||
tipper_1_initial_reserved
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 core::str;
|
||||
use sp_io::hashing::twox_128;
|
||||
|
||||
use super::super::LOG_TARGET;
|
||||
use frame_support::{
|
||||
storage::StoragePrefixedMap,
|
||||
traits::{
|
||||
Get, GetStorageVersion, PalletInfoAccess, StorageVersion,
|
||||
STORAGE_VERSION_STORAGE_KEY_POSTFIX,
|
||||
},
|
||||
weights::Weight,
|
||||
};
|
||||
|
||||
use crate as pallet_tips;
|
||||
|
||||
/// Migrate the entire storage of this pallet to a new prefix.
|
||||
///
|
||||
/// 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::<TipsPallet>`.
|
||||
///
|
||||
/// 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_tips::Config, P: GetStorageVersion + PalletInfoAccess, N: AsRef<str>>(
|
||||
old_pallet_name: N,
|
||||
) -> Weight {
|
||||
let old_pallet_name = old_pallet_name.as_ref();
|
||||
let new_pallet_name = <P as PalletInfoAccess>::name();
|
||||
|
||||
if new_pallet_name == old_pallet_name {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"New pallet name is equal to the old prefix. No migration needs to be done.",
|
||||
);
|
||||
return Weight::zero();
|
||||
}
|
||||
|
||||
let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Running migration to v4 for tips with storage version {:?}",
|
||||
on_chain_storage_version,
|
||||
);
|
||||
|
||||
if on_chain_storage_version < 4 {
|
||||
let storage_prefix = pallet_tips::Tips::<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_tips::Reasons::<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: LOG_TARGET,
|
||||
"Attempted to apply migration to v4 but failed because storage version is {:?}",
|
||||
on_chain_storage_version,
|
||||
);
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_migrate<
|
||||
T: pallet_tips::Config,
|
||||
P: GetStorageVersion + PalletInfoAccess,
|
||||
N: AsRef<str>,
|
||||
>(
|
||||
old_pallet_name: N,
|
||||
) {
|
||||
let old_pallet_name = old_pallet_name.as_ref();
|
||||
let new_pallet_name = <P as PalletInfoAccess>::name();
|
||||
|
||||
let storage_prefix_tips = pallet_tips::Tips::<T>::storage_prefix();
|
||||
let storage_prefix_reasons = pallet_tips::Reasons::<T>::storage_prefix();
|
||||
|
||||
log_migration("pre-migration", storage_prefix_tips, old_pallet_name, new_pallet_name);
|
||||
log_migration("pre-migration", storage_prefix_reasons, old_pallet_name, new_pallet_name);
|
||||
|
||||
if new_pallet_name == old_pallet_name {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_pallet_prefix = twox_128(new_pallet_name.as_bytes());
|
||||
let storage_version_key = twox_128(STORAGE_VERSION_STORAGE_KEY_POSTFIX);
|
||||
|
||||
let mut new_pallet_prefix_iter = frame_support::storage::KeyPrefixIterator::new(
|
||||
new_pallet_prefix.to_vec(),
|
||||
new_pallet_prefix.to_vec(),
|
||||
|key| Ok(key.to_vec()),
|
||||
);
|
||||
|
||||
// Ensure nothing except the storage_version_key is stored in the new prefix.
|
||||
assert!(new_pallet_prefix_iter.all(|key| key == storage_version_key));
|
||||
|
||||
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_migrate<
|
||||
T: pallet_tips::Config,
|
||||
P: GetStorageVersion + PalletInfoAccess,
|
||||
N: AsRef<str>,
|
||||
>(
|
||||
old_pallet_name: N,
|
||||
) {
|
||||
let old_pallet_name = old_pallet_name.as_ref();
|
||||
let new_pallet_name = <P as PalletInfoAccess>::name();
|
||||
|
||||
let storage_prefix_tips = pallet_tips::Tips::<T>::storage_prefix();
|
||||
let storage_prefix_reasons = pallet_tips::Reasons::<T>::storage_prefix();
|
||||
|
||||
log_migration("post-migration", storage_prefix_tips, old_pallet_name, new_pallet_name);
|
||||
log_migration("post-migration", storage_prefix_reasons, old_pallet_name, new_pallet_name);
|
||||
|
||||
if new_pallet_name == old_pallet_name {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that no `Tips` and `Reasons` storages remains at the old prefix.
|
||||
let old_pallet_prefix = twox_128(old_pallet_name.as_bytes());
|
||||
let old_tips_key = [&old_pallet_prefix, &twox_128(storage_prefix_tips)[..]].concat();
|
||||
let old_tips_key_iter = frame_support::storage::KeyPrefixIterator::new(
|
||||
old_tips_key.to_vec(),
|
||||
old_tips_key.to_vec(),
|
||||
|_| Ok(()),
|
||||
);
|
||||
assert_eq!(old_tips_key_iter.count(), 0);
|
||||
|
||||
let old_reasons_key = [&old_pallet_prefix, &twox_128(storage_prefix_reasons)[..]].concat();
|
||||
let old_reasons_key_iter = frame_support::storage::KeyPrefixIterator::new(
|
||||
old_reasons_key.to_vec(),
|
||||
old_reasons_key.to_vec(),
|
||||
|_| Ok(()),
|
||||
);
|
||||
assert_eq!(old_reasons_key_iter.count(), 0);
|
||||
|
||||
// Assert that the `Tips` and `Reasons` storages (if they exist) have been moved to the new
|
||||
// prefix.
|
||||
// NOTE: storage_version_key is already in the new prefix.
|
||||
let new_pallet_prefix = twox_128(new_pallet_name.as_bytes());
|
||||
let new_pallet_prefix_iter = frame_support::storage::KeyPrefixIterator::new(
|
||||
new_pallet_prefix.to_vec(),
|
||||
new_pallet_prefix.to_vec(),
|
||||
|_| Ok(()),
|
||||
);
|
||||
assert!(new_pallet_prefix_iter.count() >= 1);
|
||||
|
||||
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: LOG_TARGET,
|
||||
"{} prefix of storage '{}': '{}' ==> '{}'",
|
||||
stage,
|
||||
str::from_utf8(storage_prefix).unwrap_or("<Invalid UTF8>"),
|
||||
old_pallet_name,
|
||||
new_pallet_name,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Treasury pallet tests.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
traits::{BadOrigin, BlakeTwo256, IdentityLookup},
|
||||
BuildStorage, Perbill, Permill,
|
||||
};
|
||||
use sp_storage::Storage;
|
||||
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, derive_impl, parameter_types,
|
||||
storage::StoragePrefixedMap,
|
||||
traits::{
|
||||
tokens::{PayFromAccount, UnityAssetBalanceConversion},
|
||||
ConstU32, ConstU64, IntegrityTest, SortedMembers, StorageVersion,
|
||||
},
|
||||
PalletId,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::{self as pallet_tips, Event as TipEvent};
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Treasury: pallet_treasury,
|
||||
Treasury1: pallet_treasury::<Instance1>,
|
||||
Tips: pallet_tips,
|
||||
Tips1: pallet_tips::<Instance1>,
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
parameter_types! {
|
||||
static TenToFourteenTestValue: Vec<u128> = vec![10,11,12,13,14];
|
||||
}
|
||||
pub struct TenToFourteen;
|
||||
impl SortedMembers<u128> for TenToFourteen {
|
||||
fn sorted_members() -> Vec<u128> {
|
||||
TenToFourteenTestValue::get().clone()
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add(new: &u128) {
|
||||
TenToFourteenTestValue::mutate(|members| {
|
||||
members.push(*new);
|
||||
members.sort();
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ContainsLengthBound for TenToFourteen {
|
||||
fn max_len() -> usize {
|
||||
TenToFourteenTestValue::get().len()
|
||||
}
|
||||
fn min_len() -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
parameter_types! {
|
||||
pub const Burn: Permill = Permill::from_percent(50);
|
||||
pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
|
||||
pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2");
|
||||
pub TreasuryAccount: u128 = Treasury::account_id();
|
||||
pub TreasuryInstance1Account: u128 = Treasury1::account_id();
|
||||
}
|
||||
|
||||
impl pallet_treasury::Config for Test {
|
||||
type PalletId = TreasuryPalletId;
|
||||
type Currency = pallet_balances::Pallet<Test>;
|
||||
type RejectOrigin = frame_system::EnsureRoot<u128>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SpendPeriod = ConstU64<2>;
|
||||
type Burn = Burn;
|
||||
type BurnDestination = (); // Just gets burned.
|
||||
type WeightInfo = ();
|
||||
type SpendFunds = ();
|
||||
type MaxApprovals = ConstU32<100>;
|
||||
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
|
||||
type AssetKind = ();
|
||||
type Beneficiary = Self::AccountId;
|
||||
type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
|
||||
type Paymaster = PayFromAccount<Balances, TreasuryAccount>;
|
||||
type BalanceConverter = UnityAssetBalanceConversion;
|
||||
type PayoutPeriod = ConstU64<10>;
|
||||
type BlockNumberProvider = System;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
impl pallet_treasury::Config<Instance1> for Test {
|
||||
type PalletId = TreasuryPalletId2;
|
||||
type Currency = pallet_balances::Pallet<Test>;
|
||||
type RejectOrigin = frame_system::EnsureRoot<u128>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SpendPeriod = ConstU64<2>;
|
||||
type Burn = Burn;
|
||||
type BurnDestination = (); // Just gets burned.
|
||||
type WeightInfo = ();
|
||||
type SpendFunds = ();
|
||||
type MaxApprovals = ConstU32<100>;
|
||||
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
|
||||
type AssetKind = ();
|
||||
type Beneficiary = Self::AccountId;
|
||||
type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
|
||||
type Paymaster = PayFromAccount<Balances, TreasuryInstance1Account>;
|
||||
type BalanceConverter = UnityAssetBalanceConversion;
|
||||
type PayoutPeriod = ConstU64<10>;
|
||||
type BlockNumberProvider = System;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const TipFindersFee: Percent = Percent::from_percent(20);
|
||||
pub static TipReportDepositBase: u64 = 1;
|
||||
}
|
||||
impl Config for Test {
|
||||
type MaximumReasonLength = ConstU32<16384>;
|
||||
type Tippers = TenToFourteen;
|
||||
type TipCountdown = ConstU64<1>;
|
||||
type TipFindersFee = TipFindersFee;
|
||||
type TipReportDepositBase = TipReportDepositBase;
|
||||
type DataDepositPerByte = ConstU64<1>;
|
||||
type MaxTipAmount = ConstU64<10_000_000>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OnSlash = ();
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
impl Config<Instance1> for Test {
|
||||
type MaximumReasonLength = ConstU32<16384>;
|
||||
type Tippers = TenToFourteen;
|
||||
type TipCountdown = ConstU64<1>;
|
||||
type TipFindersFee = TipFindersFee;
|
||||
type TipReportDepositBase = TipReportDepositBase;
|
||||
type DataDepositPerByte = ConstU64<1>;
|
||||
type MaxTipAmount = ConstU64<10_000_000>;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OnSlash = ();
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig {
|
||||
system: frame_system::GenesisConfig::default(),
|
||||
balances: pallet_balances::GenesisConfig {
|
||||
balances: vec![(0, 100), (1, 98), (2, 1)],
|
||||
..Default::default()
|
||||
},
|
||||
treasury: Default::default(),
|
||||
treasury_1: Default::default(),
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap()
|
||||
.into();
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
/// Run the function pointer inside externalities and asserts the try_state hook at the end.
|
||||
pub fn build_and_execute(test: impl FnOnce() -> ()) {
|
||||
new_test_ext().execute_with(|| {
|
||||
test();
|
||||
Tips::do_try_state().expect("All invariants must hold after a test");
|
||||
});
|
||||
}
|
||||
|
||||
fn last_event() -> TipEvent<Test> {
|
||||
System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Tips(inner) = e { Some(inner) } else { None })
|
||||
.last()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn genesis_config_works() {
|
||||
build_and_execute(|| {
|
||||
assert_eq!(Treasury::pot(), 0);
|
||||
assert_eq!(Treasury::proposal_count(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
fn tip_hash() -> H256 {
|
||||
BlakeTwo256::hash_of(&(BlakeTwo256::hash(b"awesome.hez"), 3u128))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tip_new_cannot_be_used_twice() {
|
||||
build_and_execute(|| {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.hez".to_vec(), 3, 10));
|
||||
assert_noop!(
|
||||
Tips::tip_new(RuntimeOrigin::signed(11), b"awesome.hez".to_vec(), 3, 10),
|
||||
Error::<Test>::AlreadyKnown
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_awesome_and_tip_works() {
|
||||
build_and_execute(|| {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".to_vec(), 3));
|
||||
assert_eq!(Balances::reserved_balance(0), 12);
|
||||
assert_eq!(Balances::free_balance(0), 88);
|
||||
|
||||
// other reports don't count.
|
||||
assert_noop!(
|
||||
Tips::report_awesome(RuntimeOrigin::signed(1), b"awesome.hez".to_vec(), 3),
|
||||
Error::<Test>::AlreadyKnown
|
||||
);
|
||||
|
||||
let h = tip_hash();
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10));
|
||||
assert_noop!(Tips::tip(RuntimeOrigin::signed(9), h, 10), BadOrigin);
|
||||
System::set_block_number(2);
|
||||
assert_ok!(Tips::close_tip(RuntimeOrigin::signed(100), h.into()));
|
||||
assert_eq!(Balances::reserved_balance(0), 0);
|
||||
assert_eq!(Balances::free_balance(0), 102);
|
||||
assert_eq!(Balances::free_balance(3), 8);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_awesome_from_beneficiary_and_tip_works() {
|
||||
build_and_execute(|| {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".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.hez"), 0u128));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10));
|
||||
System::set_block_number(2);
|
||||
assert_ok!(Tips::close_tip(RuntimeOrigin::signed(100), h.into()));
|
||||
assert_eq!(Balances::reserved_balance(0), 0);
|
||||
assert_eq!(Balances::free_balance(0), 110);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_tip_works() {
|
||||
build_and_execute(|| {
|
||||
System::set_block_number(1);
|
||||
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_eq!(Treasury::pot(), 100);
|
||||
|
||||
assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.hez".to_vec(), 3, 10));
|
||||
|
||||
let h = tip_hash();
|
||||
|
||||
assert_eq!(last_event(), TipEvent::NewTip { tip_hash: h });
|
||||
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10));
|
||||
|
||||
assert_noop!(Tips::close_tip(RuntimeOrigin::signed(0), h.into()), Error::<Test>::StillOpen);
|
||||
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10));
|
||||
|
||||
assert_eq!(last_event(), TipEvent::TipClosing { tip_hash: h });
|
||||
|
||||
assert_noop!(Tips::close_tip(RuntimeOrigin::signed(0), h.into()), Error::<Test>::Premature);
|
||||
|
||||
System::set_block_number(2);
|
||||
assert_noop!(Tips::close_tip(RuntimeOrigin::none(), h.into()), BadOrigin);
|
||||
assert_ok!(Tips::close_tip(RuntimeOrigin::signed(0), h.into()));
|
||||
assert_eq!(Balances::free_balance(3), 10);
|
||||
|
||||
assert_eq!(last_event(), TipEvent::TipClosed { tip_hash: h, who: 3, payout: 10 });
|
||||
|
||||
assert_noop!(
|
||||
Tips::close_tip(RuntimeOrigin::signed(100), h.into()),
|
||||
Error::<Test>::UnknownTip
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_tip_works() {
|
||||
build_and_execute(|| {
|
||||
System::set_block_number(1);
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_eq!(Treasury::pot(), 100);
|
||||
|
||||
assert_eq!(Balances::reserved_balance(0), 0);
|
||||
assert_eq!(Balances::free_balance(0), 100);
|
||||
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".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(), TipEvent::NewTip { tip_hash: h });
|
||||
|
||||
// can't remove from any origin
|
||||
assert_noop!(Tips::slash_tip(RuntimeOrigin::signed(0), h), BadOrigin);
|
||||
|
||||
// can remove from root.
|
||||
assert_ok!(Tips::slash_tip(RuntimeOrigin::root(), h));
|
||||
assert_eq!(last_event(), TipEvent::TipSlashed { tip_hash: h, finder: 0, deposit: 12 });
|
||||
|
||||
// tipper slashed
|
||||
assert_eq!(Balances::reserved_balance(0), 0);
|
||||
assert_eq!(Balances::free_balance(0), 88);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retract_tip_works() {
|
||||
build_and_execute(|| {
|
||||
// with report awesome
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".to_vec(), 3));
|
||||
let h = tip_hash();
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10));
|
||||
assert_noop!(Tips::retract_tip(RuntimeOrigin::signed(10), h), Error::<Test>::NotFinder);
|
||||
assert_ok!(Tips::retract_tip(RuntimeOrigin::signed(0), h));
|
||||
System::set_block_number(2);
|
||||
assert_noop!(
|
||||
Tips::close_tip(RuntimeOrigin::signed(0), h.into()),
|
||||
Error::<Test>::UnknownTip
|
||||
);
|
||||
|
||||
// with tip new
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.hez".to_vec(), 3, 10));
|
||||
let h = tip_hash();
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10));
|
||||
assert_noop!(Tips::retract_tip(RuntimeOrigin::signed(0), h), Error::<Test>::NotFinder);
|
||||
assert_ok!(Tips::retract_tip(RuntimeOrigin::signed(10), h));
|
||||
System::set_block_number(2);
|
||||
assert_noop!(
|
||||
Tips::close_tip(RuntimeOrigin::signed(10), h.into()),
|
||||
Error::<Test>::UnknownTip
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tip_median_calculation_works() {
|
||||
build_and_execute(|| {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.hez".to_vec(), 3, 0));
|
||||
let h = tip_hash();
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 1000000));
|
||||
System::set_block_number(2);
|
||||
assert_ok!(Tips::close_tip(RuntimeOrigin::signed(0), h.into()));
|
||||
assert_eq!(Balances::free_balance(3), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tip_large_should_fail() {
|
||||
build_and_execute(|| {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.hez".to_vec(), 3, 0));
|
||||
let h = tip_hash();
|
||||
assert_noop!(
|
||||
Tips::tip(
|
||||
RuntimeOrigin::signed(12),
|
||||
h,
|
||||
<<Test as Config>::MaxTipAmount as Get<u64>>::get() + 1
|
||||
),
|
||||
Error::<Test>::MaxTipAmountExceeded
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tip_changing_works() {
|
||||
build_and_execute(|| {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
assert_ok!(Tips::tip_new(RuntimeOrigin::signed(10), b"awesome.hez".to_vec(), 3, 10000));
|
||||
let h = tip_hash();
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 10000));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 10000));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(13), h, 0));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(14), h, 0));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(12), h, 1000));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(11), h, 100));
|
||||
assert_ok!(Tips::tip(RuntimeOrigin::signed(10), h, 10));
|
||||
System::set_block_number(2);
|
||||
assert_ok!(Tips::close_tip(RuntimeOrigin::signed(0), h.into()));
|
||||
assert_eq!(Balances::free_balance(3), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_last_reward_migration() {
|
||||
let mut s = Storage::default();
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)]
|
||||
pub struct OldOpenTip<
|
||||
AccountId: Parameter,
|
||||
Balance: Parameter,
|
||||
BlockNumber: Parameter,
|
||||
Hash: Parameter,
|
||||
> {
|
||||
/// The hash of the reason for the tip. The reason should be a human-readable UTF-8 encoded
|
||||
/// string. A URL would be sensible.
|
||||
reason: Hash,
|
||||
/// The account to be tipped.
|
||||
who: AccountId,
|
||||
/// The account who began this tip and the amount held on deposit.
|
||||
finder: Option<(AccountId, Balance)>,
|
||||
/// The block number at which this tip will close if `Some`. If `None`, then no closing is
|
||||
/// scheduled.
|
||||
closes: Option<BlockNumber>,
|
||||
/// The members who have voted for this tip. Sorted by AccountId.
|
||||
tips: Vec<(AccountId, Balance)>,
|
||||
}
|
||||
|
||||
let reason1 = BlakeTwo256::hash(b"reason1");
|
||||
let hash1 = BlakeTwo256::hash_of(&(reason1, 10u64));
|
||||
|
||||
let old_tip_finder = OldOpenTip::<u128, u64, u64, H256> {
|
||||
reason: reason1,
|
||||
who: 10,
|
||||
finder: Some((20, 30)),
|
||||
closes: Some(13),
|
||||
tips: vec![(40, 50), (60, 70)],
|
||||
};
|
||||
|
||||
let reason2 = BlakeTwo256::hash(b"reason2");
|
||||
let hash2 = BlakeTwo256::hash_of(&(reason2, 20u64));
|
||||
|
||||
let old_tip_no_finder = OldOpenTip::<u128, u64, u64, H256> {
|
||||
reason: reason2,
|
||||
who: 20,
|
||||
finder: None,
|
||||
closes: Some(13),
|
||||
tips: vec![(40, 50), (60, 70)],
|
||||
};
|
||||
|
||||
let data = vec![
|
||||
(pallet_tips::Tips::<Test>::hashed_key_for(hash1), old_tip_finder.encode().to_vec()),
|
||||
(pallet_tips::Tips::<Test>::hashed_key_for(hash2), old_tip_no_finder.encode().to_vec()),
|
||||
];
|
||||
|
||||
s.top = data.into_iter().collect();
|
||||
|
||||
sp_io::TestExternalities::new(s).execute_with(|| {
|
||||
let module = pallet_tips::Tips::<Test>::pallet_prefix();
|
||||
let item = pallet_tips::Tips::<Test>::storage_prefix();
|
||||
Tips::migrate_retract_tip_for_tip_new(module, item);
|
||||
|
||||
// Test w/ finder
|
||||
assert_eq!(
|
||||
pallet_tips::Tips::<Test>::get(hash1),
|
||||
Some(OpenTip {
|
||||
reason: reason1,
|
||||
who: 10,
|
||||
finder: 20,
|
||||
deposit: 30,
|
||||
closes: Some(13),
|
||||
tips: vec![(40, 50), (60, 70)],
|
||||
finders_fee: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Test w/o finder
|
||||
assert_eq!(
|
||||
pallet_tips::Tips::<Test>::get(hash2),
|
||||
Some(OpenTip {
|
||||
reason: reason2,
|
||||
who: 20,
|
||||
finder: Default::default(),
|
||||
deposit: 0,
|
||||
closes: Some(13),
|
||||
tips: vec![(40, 50), (60, 70)],
|
||||
finders_fee: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_migration_v4() {
|
||||
let reason1 = BlakeTwo256::hash(b"reason1");
|
||||
let hash1 = BlakeTwo256::hash_of(&(reason1, 10u64));
|
||||
|
||||
let tip = OpenTip::<u128, u64, u64, H256> {
|
||||
reason: reason1,
|
||||
who: 10,
|
||||
finder: 20,
|
||||
deposit: 30,
|
||||
closes: Some(13),
|
||||
tips: vec![(40, 50), (60, 70)],
|
||||
finders_fee: true,
|
||||
};
|
||||
|
||||
let data = vec![
|
||||
(pallet_tips::Reasons::<Test>::hashed_key_for(hash1), reason1.encode().to_vec()),
|
||||
(pallet_tips::Tips::<Test>::hashed_key_for(hash1), tip.encode().to_vec()),
|
||||
];
|
||||
|
||||
let mut s = Storage::default();
|
||||
s.top = data.into_iter().collect();
|
||||
|
||||
sp_io::TestExternalities::new(s).execute_with(|| {
|
||||
use frame_support::traits::PalletInfoAccess;
|
||||
|
||||
let old_pallet = "Treasury";
|
||||
let new_pallet = <Tips as PalletInfoAccess>::name();
|
||||
frame_support::storage::migration::move_pallet(
|
||||
new_pallet.as_bytes(),
|
||||
old_pallet.as_bytes(),
|
||||
);
|
||||
StorageVersion::new(0).put::<Tips>();
|
||||
|
||||
crate::migrations::v4::pre_migrate::<Test, Tips, _>(old_pallet);
|
||||
crate::migrations::v4::migrate::<Test, Tips, _>(old_pallet);
|
||||
crate::migrations::v4::post_migrate::<Test, Tips, _>(old_pallet);
|
||||
});
|
||||
|
||||
sp_io::TestExternalities::new(Storage::default()).execute_with(|| {
|
||||
use frame_support::traits::PalletInfoAccess;
|
||||
|
||||
let old_pallet = "Treasury";
|
||||
let new_pallet = <Tips as PalletInfoAccess>::name();
|
||||
frame_support::storage::migration::move_pallet(
|
||||
new_pallet.as_bytes(),
|
||||
old_pallet.as_bytes(),
|
||||
);
|
||||
StorageVersion::new(0).put::<Tips>();
|
||||
|
||||
crate::migrations::v4::pre_migrate::<Test, Tips, _>(old_pallet);
|
||||
crate::migrations::v4::migrate::<Test, Tips, _>(old_pallet);
|
||||
crate::migrations::v4::post_migrate::<Test, Tips, _>(old_pallet);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genesis_funding_works() {
|
||||
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
let initial_funding = 100;
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
// Total issuance will be 200 with treasury account initialized with 100.
|
||||
balances: vec![(0, 100), (Treasury::account_id(), initial_funding)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
pallet_treasury::GenesisConfig::<Test>::default()
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
let mut t: sp_io::TestExternalities = t.into();
|
||||
|
||||
t.execute_with(|| {
|
||||
assert_eq!(Balances::free_balance(Treasury::account_id()), initial_funding);
|
||||
assert_eq!(Treasury::pot(), initial_funding - Balances::minimum_balance());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_awesome_and_tip_works_second_instance() {
|
||||
build_and_execute(|| {
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
Balances::make_free_balance_be(&Treasury1::account_id(), 201);
|
||||
assert_eq!(Balances::free_balance(&Treasury::account_id()), 101);
|
||||
assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201);
|
||||
|
||||
assert_ok!(Tips1::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".to_vec(), 3));
|
||||
// duplicate report in tips1 reports don't count.
|
||||
assert_noop!(
|
||||
Tips1::report_awesome(RuntimeOrigin::signed(1), b"awesome.hez".to_vec(), 3),
|
||||
Error::<Test, Instance1>::AlreadyKnown
|
||||
);
|
||||
// but tips is separate
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".to_vec(), 3));
|
||||
|
||||
let h = tip_hash();
|
||||
assert_ok!(Tips1::tip(RuntimeOrigin::signed(10), h, 10));
|
||||
assert_ok!(Tips1::tip(RuntimeOrigin::signed(11), h, 10));
|
||||
assert_ok!(Tips1::tip(RuntimeOrigin::signed(12), h, 10));
|
||||
assert_noop!(Tips1::tip(RuntimeOrigin::signed(9), h, 10), BadOrigin);
|
||||
|
||||
System::set_block_number(2);
|
||||
|
||||
assert_ok!(Tips1::close_tip(RuntimeOrigin::signed(100), h.into()));
|
||||
// Treasury 1 unchanged
|
||||
assert_eq!(Balances::free_balance(&Treasury::account_id()), 101);
|
||||
// Treasury 2 gave the funds
|
||||
assert_eq!(Balances::free_balance(&Treasury1::account_id()), 191);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal_entries_invariant() {
|
||||
new_test_ext().execute_with(|| {
|
||||
use frame_support::pallet_prelude::DispatchError::Other;
|
||||
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".to_vec(), 3));
|
||||
|
||||
let reason1 = BlakeTwo256::hash(b"reason1");
|
||||
let hash1 = BlakeTwo256::hash_of(&(reason1, 10u64));
|
||||
|
||||
let tip = OpenTip::<u128, u64, u64, H256> {
|
||||
reason: reason1,
|
||||
who: 10,
|
||||
finder: 20,
|
||||
deposit: 30,
|
||||
closes: Some(13),
|
||||
tips: vec![(40, 50), (60, 70)],
|
||||
finders_fee: true,
|
||||
};
|
||||
|
||||
// Breaks invariant by adding an entry to only `Tips` Storage.
|
||||
pallet_tips::Tips::<Test>::insert(hash1, tip);
|
||||
|
||||
// Invariant violated
|
||||
assert_eq!(
|
||||
Tips::do_try_state(),
|
||||
Err(Other("Equal length of entries in `Tips` and `Reasons` Storage"))
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finders_fee_invariant() {
|
||||
new_test_ext().execute_with(|| {
|
||||
use frame_support::pallet_prelude::DispatchError::Other;
|
||||
|
||||
// Breaks invariant by having a zero deposit.
|
||||
TipReportDepositBase::set(0);
|
||||
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"".to_vec(), 3));
|
||||
|
||||
// Invariant violated
|
||||
assert_eq!(
|
||||
Tips::do_try_state(),
|
||||
Err(Other("Tips with `finders_fee` should have non-zero `deposit`."))
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasons_invariant() {
|
||||
new_test_ext().execute_with(|| {
|
||||
use frame_support::pallet_prelude::DispatchError::Other;
|
||||
|
||||
Balances::make_free_balance_be(&Treasury::account_id(), 101);
|
||||
|
||||
assert_ok!(Tips::report_awesome(RuntimeOrigin::signed(0), b"awesome.hez".to_vec(), 0));
|
||||
|
||||
let hash: Vec<_> = pallet_tips::Tips::<Test>::iter_keys().collect();
|
||||
|
||||
let mut open_tip = pallet_tips::Tips::<Test>::take(hash[0]).unwrap();
|
||||
|
||||
// Breaks invariant by changing value `open_tip.reason` in `Tips` Storage.
|
||||
open_tip.reason = <Test as frame_system::Config>::Hashing::hash(&b"".to_vec());
|
||||
|
||||
pallet_tips::Tips::<Test>::insert(hash[0], open_tip);
|
||||
|
||||
// Invariant violated
|
||||
assert_eq!(Tips::do_try_state(), Err(Other("no reason for this tip")));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "`TipReportDepositBase` should not be zero"]
|
||||
fn zero_base_deposit_prohibited() {
|
||||
new_test_ext().execute_with(|| {
|
||||
TipReportDepositBase::set(0);
|
||||
Tips::integrity_test();
|
||||
});
|
||||
}
|
||||
Generated
+300
@@ -0,0 +1,300 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Autogenerated weights for `pallet_tips`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pallet_tips
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/substrate/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/substrate/frame/tips/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=substrate/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage,pallet_election_provider_multi_block,pallet_election_provider_multi_block::signed,pallet_election_provider_multi_block::unsigned,pallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pallet_tips`.
|
||||
pub trait WeightInfo {
|
||||
fn report_awesome(r: u32, ) -> Weight;
|
||||
fn retract_tip() -> Weight;
|
||||
fn tip_new(r: u32, t: u32, ) -> Weight;
|
||||
fn tip(t: u32, ) -> Weight;
|
||||
fn close_tip(t: u32, ) -> Weight;
|
||||
fn slash_tip(t: u32, ) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pallet_tips` using the Substrate node and recommended hardware.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
/// Storage: `Tips::Reasons` (r:1 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `r` is `[0, 300]`.
|
||||
fn report_awesome(r: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `3465`
|
||||
// Minimum execution time: 25_299_000 picoseconds.
|
||||
Weight::from_parts(25_994_435, 3465)
|
||||
// Standard Error: 138
|
||||
.saturating_add(Weight::from_parts(1_316, 0).saturating_mul(r.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Reasons` (r:0 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn retract_tip() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `199`
|
||||
// Estimated: `3664`
|
||||
// Minimum execution time: 24_924_000 picoseconds.
|
||||
Weight::from_parts(25_470_000, 3664)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Elections::Members` (r:1 w:0)
|
||||
/// Proof: `Elections::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Reasons` (r:1 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Tips` (r:0 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `r` is `[0, 300]`.
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn tip_new(r: u32, t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `8 + t * (64 ±0)`
|
||||
// Estimated: `3473 + t * (64 ±0)`
|
||||
// Minimum execution time: 13_938_000 picoseconds.
|
||||
Weight::from_parts(14_044_481, 3473)
|
||||
// Standard Error: 61
|
||||
.saturating_add(Weight::from_parts(1_837, 0).saturating_mul(r.into()))
|
||||
// Standard Error: 1_459
|
||||
.saturating_add(Weight::from_parts(34_960, 0).saturating_mul(t.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
.saturating_add(Weight::from_parts(0, 64).saturating_mul(t.into()))
|
||||
}
|
||||
/// Storage: `Elections::Members` (r:1 w:0)
|
||||
/// Proof: `Elections::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn tip(t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `211 + t * (112 ±0)`
|
||||
// Estimated: `3676 + t * (112 ±0)`
|
||||
// Minimum execution time: 13_128_000 picoseconds.
|
||||
Weight::from_parts(13_608_148, 3676)
|
||||
// Standard Error: 1_708
|
||||
.saturating_add(Weight::from_parts(125_669, 0).saturating_mul(t.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into()))
|
||||
}
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Elections::Members` (r:1 w:0)
|
||||
/// Proof: `Elections::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tips::Reasons` (r:0 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn close_tip(t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `211 + t * (112 ±0)`
|
||||
// Estimated: `3676 + t * (112 ±0)`
|
||||
// Minimum execution time: 54_188_000 picoseconds.
|
||||
Weight::from_parts(55_290_996, 3676)
|
||||
// Standard Error: 4_985
|
||||
.saturating_add(Weight::from_parts(110_826, 0).saturating_mul(t.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into()))
|
||||
}
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Reasons` (r:0 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn slash_tip(t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `3712`
|
||||
// Minimum execution time: 11_654_000 picoseconds.
|
||||
Weight::from_parts(12_425_890, 3712)
|
||||
// Standard Error: 1_521
|
||||
.saturating_add(Weight::from_parts(9_533, 0).saturating_mul(t.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `Tips::Reasons` (r:1 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `r` is `[0, 300]`.
|
||||
fn report_awesome(r: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `3465`
|
||||
// Minimum execution time: 25_299_000 picoseconds.
|
||||
Weight::from_parts(25_994_435, 3465)
|
||||
// Standard Error: 138
|
||||
.saturating_add(Weight::from_parts(1_316, 0).saturating_mul(r.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Reasons` (r:0 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
fn retract_tip() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `199`
|
||||
// Estimated: `3664`
|
||||
// Minimum execution time: 24_924_000 picoseconds.
|
||||
Weight::from_parts(25_470_000, 3664)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Elections::Members` (r:1 w:0)
|
||||
/// Proof: `Elections::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Reasons` (r:1 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Tips` (r:0 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `r` is `[0, 300]`.
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn tip_new(r: u32, t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `8 + t * (64 ±0)`
|
||||
// Estimated: `3473 + t * (64 ±0)`
|
||||
// Minimum execution time: 13_938_000 picoseconds.
|
||||
Weight::from_parts(14_044_481, 3473)
|
||||
// Standard Error: 61
|
||||
.saturating_add(Weight::from_parts(1_837, 0).saturating_mul(r.into()))
|
||||
// Standard Error: 1_459
|
||||
.saturating_add(Weight::from_parts(34_960, 0).saturating_mul(t.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
.saturating_add(Weight::from_parts(0, 64).saturating_mul(t.into()))
|
||||
}
|
||||
/// Storage: `Elections::Members` (r:1 w:0)
|
||||
/// Proof: `Elections::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn tip(t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `211 + t * (112 ±0)`
|
||||
// Estimated: `3676 + t * (112 ±0)`
|
||||
// Minimum execution time: 13_128_000 picoseconds.
|
||||
Weight::from_parts(13_608_148, 3676)
|
||||
// Standard Error: 1_708
|
||||
.saturating_add(Weight::from_parts(125_669, 0).saturating_mul(t.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into()))
|
||||
}
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Elections::Members` (r:1 w:0)
|
||||
/// Proof: `Elections::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Tips::Reasons` (r:0 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn close_tip(t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `211 + t * (112 ±0)`
|
||||
// Estimated: `3676 + t * (112 ±0)`
|
||||
// Minimum execution time: 54_188_000 picoseconds.
|
||||
Weight::from_parts(55_290_996, 3676)
|
||||
// Standard Error: 4_985
|
||||
.saturating_add(Weight::from_parts(110_826, 0).saturating_mul(t.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into()))
|
||||
}
|
||||
/// Storage: `Tips::Tips` (r:1 w:1)
|
||||
/// Proof: `Tips::Tips` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Tips::Reasons` (r:0 w:1)
|
||||
/// Proof: `Tips::Reasons` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `t` is `[1, 13]`.
|
||||
fn slash_tip(t: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `3712`
|
||||
// Minimum execution time: 11_654_000 picoseconds.
|
||||
Weight::from_parts(12_425_890, 3712)
|
||||
// Standard Error: 1_521
|
||||
.saturating_add(Weight::from_parts(9_533, 0).saturating_mul(t.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user