feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+67
View File
@@ -0,0 +1,67 @@
[package]
name = "pezpallet-tips"
version = "27.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "FRAME pallet to manage tips"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
pezframe-benchmarking = { optional = true, workspace = true }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
log = { workspace = true }
pezpallet-treasury = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
serde = { features = [
"derive",
], optional = true, workspace = true, default-features = true }
pezsp-core = { workspace = true }
pezsp-io = { workspace = true }
pezsp-runtime = { workspace = true }
[dev-dependencies]
pezpallet-balances = { workspace = true, default-features = true }
pezsp-storage = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"pezframe-benchmarking?/std",
"pezframe-support/std",
"pezframe-system/std",
"log/std",
"pezpallet-treasury/std",
"scale-info/std",
"serde",
"pezsp-core/std",
"pezsp-io/std",
"pezsp-runtime/std",
]
runtime-benchmarks = [
"pezframe-benchmarking/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-treasury/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
]
try-runtime = [
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-balances/try-runtime",
"pezpallet-treasury/try-runtime",
"pezsp-runtime/try-runtime",
]
+33
View File
@@ -0,0 +1,33 @@
# Tipping Pallet ( pezpallet-tips )
**Note :: This pallet is tightly coupled to pezpallet-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:** 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
- `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.
- `slash_tip` - Remove and slash an already-open tip.
@@ -0,0 +1,206 @@
// This file is part of Bizinikiwi.
// 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 pezframe_benchmarking::v1::{
account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError,
};
use pezframe_support::ensure;
use pezframe_system::RawOrigin;
use pezsp_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(pezframe_system::pezpallet_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 = pezframe_system::Account::<T>::hashed_key_for(&caller);
pezframe_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 = pezframe_system::Account::<T>::hashed_key_for(&caller);
pezframe_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 = pezframe_system::Account::<T>::hashed_key_for(&caller);
pezframe_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 = pezframe_system::Account::<T>::hashed_key_for(&caller);
pezframe_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 = pezframe_system::Account::<T>::hashed_key_for(&caller);
pezframe_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);
}
+687
View File
@@ -0,0 +1,687 @@
// This file is part of Bizinikiwi.
// 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 ( pezpallet-tips )
//!
//! > NOTE: This pallet is tightly coupled with pezpallet-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 pezsp_runtime::{
traits::{AccountIdConversion, BadOrigin, Hash, StaticLookup, TrailingZeroInput, Zero},
Percent, RuntimeDebug,
};
use alloc::{vec, vec::Vec};
use codec::{Decode, Encode};
use pezframe_support::{
ensure,
traits::{
ContainsLengthBound, Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, Get,
OnUnbalanced, ReservableCurrency, SortedMembers,
},
Parameter,
};
use pezframe_system::pezpallet_prelude::BlockNumberFor;
#[cfg(any(feature = "try-runtime", test))]
use pezsp_runtime::TryRuntimeError;
pub use pallet::*;
pub use weights::WeightInfo;
const LOG_TARGET: &str = "runtime::tips";
pub type BalanceOf<T, I = ()> = pezpallet_treasury::BalanceOf<T, I>;
pub type NegativeImbalanceOf<T, I = ()> = pezpallet_treasury::NegativeImbalanceOf<T, I>;
type AccountIdLookupOf<T> = <<T as pezframe_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,
}
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_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 = ()>: pezframe_system::Config + pezpallet_treasury::Config<I> {
/// The overarching event type.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as pezframe_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!(pezframe_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(pezframe_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 = pezpallet_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 pezframe_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 Bizinikiwi.
// 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, pezpallet-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 Bizinikiwi.
// 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 pezframe_support::{
pezpallet_prelude::OptionQuery,
storage_alias,
traits::{Currency, LockableCurrency, OnRuntimeUpgrade, ReservableCurrency},
weights::RuntimeDbWeight,
Parameter, Twox64Concat,
};
use pezsp_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: pezsp_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: pezsp_core::Get<BalanceOf<Self, I>>;
/// The name of the pallet as previously configured in
/// [`construct_runtime!`](pezframe_support::construct_runtime).
type PalletName: pezsp_core::Get<&'static str>;
/// The DB weight as configured in the runtime to calculate the correct weight.
type DbWeight: pezsp_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
/// * `pezframe_support::weights::Weight`: The weight of this operation.
fn get_deposits() -> (BTreeMap<T::AccountId, BalanceOf<T, I>>, pezframe_support::weights::Weight) {
use pezsp_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>, pezsp_runtime::TryRuntimeError> {
use codec::Encode;
use pezframe_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() -> pezframe_support::weights::Weight {
use pezframe_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<(), pezsp_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 pezframe_support::{assert_ok, parameter_types, traits::TypedGet};
use pezframe_system::pezpallet_prelude::BlockNumberFor;
use pezsp_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 = pezsp_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 pezpallet_treasury::Config>::Currency::reserve(
&tipper_0,
tipper_0_initial_reserved
));
assert_ok!(<Test as pezpallet_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 pezpallet_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 pezpallet_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 pezpallet_treasury::Config>::Currency::reserved_balance(&tipper_0),
tipper_0_initial_reserved
);
assert_eq!(
<Test as pezpallet_treasury::Config>::Currency::reserved_balance(&tipper_1),
tipper_1_initial_reserved
);
});
}
}
@@ -0,0 +1,196 @@
// This file is part of Bizinikiwi.
// 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 pezsp_io::hashing::twox_128;
use super::super::LOG_TARGET;
use pezframe_support::{
storage::StoragePrefixedMap,
traits::{
Get, GetStorageVersion, PalletInfoAccess, StorageVersion,
STORAGE_VERSION_STORAGE_KEY_POSTFIX,
},
weights::Weight,
};
use crate as pezpallet_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 pezframe_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: pezpallet_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 = pezpallet_tips::Tips::<T>::storage_prefix();
pezframe_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 = pezpallet_tips::Reasons::<T>::storage_prefix();
pezframe_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 pezframe_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
/// `pezframe_support::traits::OnRuntimeUpgrade::pre_upgrade` for further testing.
///
/// Panics if anything goes wrong.
pub fn pre_migrate<
T: pezpallet_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 = pezpallet_tips::Tips::<T>::storage_prefix();
let storage_prefix_reasons = pezpallet_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 = pezframe_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
/// `pezframe_support::traits::OnRuntimeUpgrade::post_upgrade` for further testing.
///
/// Panics if anything goes wrong.
pub fn post_migrate<
T: pezpallet_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 = pezpallet_tips::Tips::<T>::storage_prefix();
let storage_prefix_reasons = pezpallet_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 = pezframe_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 = pezframe_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 = pezframe_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,
);
}
+720
View File
@@ -0,0 +1,720 @@
// This file is part of Bizinikiwi.
// 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 pezsp_core::H256;
use pezsp_runtime::{
traits::{BadOrigin, BlakeTwo256, IdentityLookup},
BuildStorage, Perbill, Permill,
};
use pezsp_storage::Storage;
use pezframe_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 pezpallet_tips, Event as TipEvent};
type Block = pezframe_system::mocking::MockBlock<Test>;
pezframe_support::construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
Treasury: pezpallet_treasury,
Treasury1: pezpallet_treasury::<Instance1>,
Tips: pezpallet_tips,
Tips1: pezpallet_tips::<Instance1>,
}
);
parameter_types! {
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_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 = pezpallet_balances::AccountData<u64>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_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 pezpallet_treasury::Config for Test {
type PalletId = TreasuryPalletId;
type Currency = pezpallet_balances::Pallet<Test>;
type RejectOrigin = pezframe_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 = pezframe_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 pezpallet_treasury::Config<Instance1> for Test {
type PalletId = TreasuryPalletId2;
type Currency = pezpallet_balances::Pallet<Test>;
type RejectOrigin = pezframe_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 = pezframe_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() -> pezsp_io::TestExternalities {
let mut ext: pezsp_io::TestExternalities = RuntimeGenesisConfig {
system: pezframe_system::GenesisConfig::default(),
balances: pezpallet_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![
(pezpallet_tips::Tips::<Test>::hashed_key_for(hash1), old_tip_finder.encode().to_vec()),
(pezpallet_tips::Tips::<Test>::hashed_key_for(hash2), old_tip_no_finder.encode().to_vec()),
];
s.top = data.into_iter().collect();
pezsp_io::TestExternalities::new(s).execute_with(|| {
let module = pezpallet_tips::Tips::<Test>::pezpallet_prefix();
let item = pezpallet_tips::Tips::<Test>::storage_prefix();
Tips::migrate_retract_tip_for_tip_new(module, item);
// Test w/ finder
assert_eq!(
pezpallet_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!(
pezpallet_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![
(pezpallet_tips::Reasons::<Test>::hashed_key_for(hash1), reason1.encode().to_vec()),
(pezpallet_tips::Tips::<Test>::hashed_key_for(hash1), tip.encode().to_vec()),
];
let mut s = Storage::default();
s.top = data.into_iter().collect();
pezsp_io::TestExternalities::new(s).execute_with(|| {
use pezframe_support::traits::PalletInfoAccess;
let old_pallet = "Treasury";
let new_pallet = <Tips as PalletInfoAccess>::name();
pezframe_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);
});
pezsp_io::TestExternalities::new(Storage::default()).execute_with(|| {
use pezframe_support::traits::PalletInfoAccess;
let old_pallet = "Treasury";
let new_pallet = <Tips as PalletInfoAccess>::name();
pezframe_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 = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let initial_funding = 100;
pezpallet_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();
pezpallet_treasury::GenesisConfig::<Test>::default()
.assimilate_storage(&mut t)
.unwrap();
let mut t: pezsp_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 pezframe_support::pezpallet_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.
pezpallet_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 pezframe_support::pezpallet_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 pezframe_support::pezpallet_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<_> = pezpallet_tips::Tips::<Test>::iter_keys().collect();
let mut open_tip = pezpallet_tips::Tips::<Test>::take(hash[0]).unwrap();
// Breaks invariant by changing value `open_tip.reason` in `Tips` Storage.
open_tip.reason = <Test as pezframe_system::Config>::Hashing::hash(&b"".to_vec());
pezpallet_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();
});
}
+300
View File
@@ -0,0 +1,300 @@
// This file is part of Bizinikiwi.
// 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 Bizinikiwi.
// 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 `pezpallet_tips`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI 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=pezpallet_tips
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/tips/src/weights.rs
// --wasm-execution=compiled
// --steps=50
// --repeat=20
// --heap-pages=4096
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
// --no-storage-info
// --no-min-squares
// --no-median-slopes
// --genesis-builder-policy=none
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pezpallet_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 `pezpallet_tips` using the Bizinikiwi node and recommended hardware.
pub struct BizinikiwiWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<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))
}
}