Non-Interactive Staking (#12610)

* Improve naming.

* More improvements to naming

* Fungible counterpart

* Shared pot instead of reserve

* Transferable receipts

* Better naming

* Use u128 for counterpart

* Partial thawing

* Docs

* Remove AdminOrigin

* Integrate into Kitchen Sink

* Thaw throttling

* Remove todo

* Docs

* Fix benchmarks

* Building

* Tests work

* New benchmarks

* Benchmarking tests

* Test new defensive_saturating_* functions

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Formatting

* Update frame/nis/src/lib.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Apply suggestions from code review

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Events added

* Fix kitchensink

* Update frame/nis/src/lib.rs

Co-authored-by: Xiliang Chen <xlchen1291@gmail.com>

* Review niggles

* Remove genesis build requirements

* Grumbles

* Fixes

* Fixes

* Fixes

* Update frame/nis/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Update primitives/runtime/src/traits.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Formatting

* Fixes

* Fix node genesis config

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix node chain specs

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Use free asset ID as counterpart

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Account for rounding errors in fund_deficit bench

Relaxes the check for the NIS account balance in the fund_deficit bench
from equality from to checking for 99.999% equality. The exact deviation
for the kitchensink runtime config is 1.24e-10 percent but could vary if
the config is changed.

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* clippy

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* fmt

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix

* Rename

* Fixes

* Fixes

* Formatting

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Xiliang Chen <xlchen1291@gmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
Gavin Wood
2022-12-05 13:37:52 +00:00
committed by GitHub
parent f9f1ac2515
commit 2a0e53d11a
25 changed files with 2311 additions and 1664 deletions
+182
View File
@@ -0,0 +1,182 @@
// This file is part of Substrate.
// Copyright (C) 2021-2022 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.
//! Benchmarks for NIS Pallet
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::{account, benchmarks, whitelisted_caller};
use frame_support::traits::{Currency, EnsureOrigin, Get};
use frame_system::RawOrigin;
use sp_arithmetic::Perquintill;
use sp_runtime::{
traits::{Bounded, One, Zero},
DispatchError, PerThing,
};
use sp_std::prelude::*;
use crate::Pallet as Nis;
const SEED: u32 = 0;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
fn fill_queues<T: Config>() -> Result<(), DispatchError> {
// filling queues involves filling the first queue entirely and placing a single item in all
// other queues.
let queues = T::QueueCount::get();
let bids = T::MaxQueueLen::get();
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(
&caller,
T::MinBid::get() * BalanceOf::<T>::from(queues + bids),
);
for _ in 0..bids {
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
}
for d in 1..queues {
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1 + d)?;
}
Ok(())
}
benchmarks! {
place_bid {
let l in 0..(T::MaxQueueLen::get() - 1);
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
for i in 0..l {
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
}
}: _(RawOrigin::Signed(caller.clone()), T::MinBid::get() * BalanceOf::<T>::from(2u32), 1)
verify {
assert_eq!(QueueTotals::<T>::get()[0], (l + 1, T::MinBid::get() * BalanceOf::<T>::from(l + 2)));
}
place_bid_max {
let caller: T::AccountId = whitelisted_caller();
let origin = RawOrigin::Signed(caller.clone());
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
for i in 0..T::MaxQueueLen::get() {
Nis::<T>::place_bid(origin.clone().into(), T::MinBid::get(), 1)?;
}
}: place_bid(origin, T::MinBid::get() * BalanceOf::<T>::from(2u32), 1)
verify {
assert_eq!(QueueTotals::<T>::get()[0], (
T::MaxQueueLen::get(),
T::MinBid::get() * BalanceOf::<T>::from(T::MaxQueueLen::get() + 1),
));
}
retract_bid {
let l in 1..T::MaxQueueLen::get();
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
for i in 0..l {
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
}
}: _(RawOrigin::Signed(caller.clone()), T::MinBid::get(), 1)
verify {
assert_eq!(QueueTotals::<T>::get()[0], (l - 1, T::MinBid::get() * BalanceOf::<T>::from(l - 1)));
}
fund_deficit {
let origin = T::FundOrigin::successful_origin();
let caller: T::AccountId = whitelisted_caller();
let bid = T::MinBid::get().max(One::one());
T::Currency::make_free_balance_be(&caller, bid);
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 1, &mut WeightCounter::unlimited());
let original = T::Currency::free_balance(&Nis::<T>::account_id());
T::Currency::make_free_balance_be(&Nis::<T>::account_id(), BalanceOf::<T>::min_value());
}: _<T::RuntimeOrigin>(origin)
verify {
// Must fund at least 99.999% of the required amount.
let missing = Perquintill::from_rational(
T::Currency::free_balance(&Nis::<T>::account_id()), original).left_from_one();
assert!(missing <= Perquintill::one() / 100_000);
}
thaw {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::<T>::from(3u32));
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
Nis::<T>::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?;
Nis::<T>::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited());
Receipts::<T>::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() });
}: _(RawOrigin::Signed(caller.clone()), 0, None)
verify {
assert!(Receipts::<T>::get(0).is_none());
}
process_queues {
fill_queues::<T>()?;
}: {
Nis::<T>::process_queues(
Perquintill::one(),
Zero::zero(),
u32::max_value(),
&mut WeightCounter::unlimited(),
)
}
process_queue {
let our_account = Nis::<T>::account_id();
let issuance = Nis::<T>::issuance();
let mut summary = Summary::<T>::get();
}: {
Nis::<T>::process_queue(
1u32,
1u32.into(),
&our_account,
&issuance,
0,
&mut Bounded::max_value(),
&mut (T::MaxQueueLen::get(), Bounded::max_value()),
&mut summary,
&mut WeightCounter::unlimited(),
)
}
process_bid {
let who = account::<T::AccountId>("bidder", 0, SEED);
let bid = Bid {
amount: T::MinBid::get(),
who,
};
let our_account = Nis::<T>::account_id();
let issuance = Nis::<T>::issuance();
let mut summary = Summary::<T>::get();
}: {
Nis::<T>::process_bid(
bid,
2u32.into(),
&our_account,
&issuance,
&mut Bounded::max_value(),
&mut Bounded::max_value(),
&mut summary,
)
}
impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext(), crate::mock::Test);
}
+936
View File
@@ -0,0 +1,936 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! # Non-Interactive Staking (NIS) Pallet
//! A pallet allowing accounts to auction for being frozen and receive open-ended
//! inflation-protection in return.
//!
//! ## Overview
//!
//! Lock up tokens, for at least as long as you offer, and be free from both inflation and
//! intermediate reward or exchange until the tokens become unlocked.
//!
//! ## Design
//!
//! Queues for each of `1..QueueCount` periods, given in blocks (`Period`). Queues are limited in
//! size to something sensible, `MaxQueueLen`. A secondary storage item with `QueueCount` x `u32`
//! elements with the number of items in each queue.
//!
//! Queues are split into two parts. The first part is a priority queue based on bid size. The
//! second part is just a FIFO (the size of the second part is set with `FifoQueueLen`). Items are
//! always prepended so that removal is always O(1) since removal often happens many times under a
//! single weighed function (`on_initialize`) yet placing bids only ever happens once per weighed
//! function (`place_bid`). If the queue has a priority portion, then it remains sorted in order of
//! bid size so that smaller bids fall off as it gets too large.
//!
//! Account may enqueue a balance with some number of `Period`s lock up, up to a maximum of
//! `QueueCount`. The balance gets reserved. There's a minimum of `MinBid` to avoid dust.
//!
//! Until your bid is consolidated and you receive a receipt, you can retract it instantly and the
//! funds are unreserved.
//!
//! There's a target proportion of effective total issuance (i.e. accounting for existing receipts)
//! which the pallet attempts to have frozen at any one time. It will likely be gradually increased
//! over time by governance.
//!
//! As the proportion of effective total issuance represented by outstanding receipts drops below
//! `FrozenFraction`, then bids are taken from queues and consolidated into receipts, with the queue
//! of the greatest period taking priority. If the item in the queue's locked amount is greater than
//! the amount remaining to achieve `FrozenFraction`, then it is split up into multiple bids and
//! becomes partially consolidated.
//!
//! With the consolidation of a bid, the bid amount is taken from the owner and a receipt is issued.
//! The receipt records the proportion of the bid compared to effective total issuance at the time
//! of consolidation. The receipt has two independent elements: a "main" non-fungible receipt and
//! a second set of fungible "counterpart" tokens. The accounting functionality of the latter must
//! be provided through the `Counterpart` trait item. The main non-fungible receipt may have its
//! owner transferred through the pallet's implementation of `nonfungible::Transfer`.
//!
//! A later `thaw` function may be called in order to reduce the recorded proportion or entirely
//! remove the receipt in return for the appropriate proportion of the effective total issuance.
//! This may happen no earlier than queue's period after the point at which the receipt was issued.
//! The call must be made by the owner of both the "main" non-fungible receipt and the appropriate
//! amount of counterpart tokens.
//!
//! `NoCounterpart` may be provided as an implementation for the counterpart token system in which
//! case they are completely disregarded from the thawing logic.
//!
//! ## Terms
//!
//! - *Effective total issuance*: The total issuance of balances in the system, including all claims
//! of all outstanding receipts but excluding `IgnoredIssuance`.
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
dispatch::{DispatchError, DispatchResult},
traits::fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate},
};
pub use pallet::*;
use sp_arithmetic::{traits::Unsigned, RationalArg};
use sp_core::TypedGet;
use sp_runtime::{
traits::{Convert, ConvertBack},
Perquintill,
};
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
pub struct WithMaximumOf<A: TypedGet>(sp_std::marker::PhantomData<A>);
impl<A: TypedGet> Convert<Perquintill, A::Type> for WithMaximumOf<A>
where
A::Type: Clone + Unsigned + From<u64>,
u64: TryFrom<A::Type>,
{
fn convert(a: Perquintill) -> A::Type {
a * A::get()
}
}
impl<A: TypedGet> ConvertBack<Perquintill, A::Type> for WithMaximumOf<A>
where
A::Type: RationalArg + From<u64>,
u64: TryFrom<A::Type>,
u128: TryFrom<A::Type>,
{
fn convert_back(a: A::Type) -> Perquintill {
Perquintill::from_rational(a, A::get())
}
}
pub struct NoCounterpart<T>(sp_std::marker::PhantomData<T>);
impl<T> FungibleInspect<T> for NoCounterpart<T> {
type Balance = u32;
fn total_issuance() -> u32 {
0
}
fn minimum_balance() -> u32 {
0
}
fn balance(_who: &T) -> u32 {
0
}
fn reducible_balance(_who: &T, _keep_alive: bool) -> u32 {
0
}
fn can_deposit(
_who: &T,
_amount: u32,
_mint: bool,
) -> frame_support::traits::tokens::DepositConsequence {
frame_support::traits::tokens::DepositConsequence::Success
}
fn can_withdraw(
_who: &T,
_amount: u32,
) -> frame_support::traits::tokens::WithdrawConsequence<u32> {
frame_support::traits::tokens::WithdrawConsequence::Success
}
}
impl<T> FungibleMutate<T> for NoCounterpart<T> {
fn mint_into(_who: &T, _amount: u32) -> DispatchResult {
Ok(())
}
fn burn_from(_who: &T, _amount: u32) -> Result<u32, DispatchError> {
Ok(0)
}
}
impl<T> Convert<Perquintill, u32> for NoCounterpart<T> {
fn convert(_: Perquintill) -> u32 {
0
}
}
#[frame_support::pallet]
pub mod pallet {
use super::{FungibleInspect, FungibleMutate};
pub use crate::weights::WeightInfo;
use frame_support::{
pallet_prelude::*,
traits::{
nonfungible::{Inspect as NonfungibleInspect, Transfer as NonfungibleTransfer},
Currency, Defensive, DefensiveSaturating,
ExistenceRequirement::AllowDeath,
OnUnbalanced, ReservableCurrency,
},
PalletId,
};
use frame_system::pallet_prelude::*;
use sp_arithmetic::{PerThing, Perquintill};
use sp_runtime::{
traits::{AccountIdConversion, Bounded, Convert, ConvertBack, Saturating, Zero},
TokenError,
};
use sp_std::prelude::*;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::PositiveImbalance;
type ReceiptRecordOf<T> = ReceiptRecord<
<T as frame_system::Config>::AccountId,
<T as frame_system::Config>::BlockNumber,
>;
type IssuanceInfoOf<T> = IssuanceInfo<BalanceOf<T>>;
type SummaryRecordOf<T> = SummaryRecord<<T as frame_system::Config>::BlockNumber>;
type BidOf<T> = Bid<BalanceOf<T>, <T as frame_system::Config>::AccountId>;
type QueueTotalsTypeOf<T> = BoundedVec<(u32, BalanceOf<T>), <T as Config>::QueueCount>;
#[pallet::config]
pub trait Config: frame_system::Config {
/// Information on runtime weights.
type WeightInfo: WeightInfo;
/// Overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The treasury's pallet id, used for deriving its sovereign account ID.
#[pallet::constant]
type PalletId: Get<PalletId>;
/// Currency type that this works on.
type Currency: ReservableCurrency<Self::AccountId, Balance = Self::CurrencyBalance>;
/// Just the `Currency::Balance` type; we have this item to allow us to constrain it to
/// `From<u64>`.
type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned
+ codec::FullCodec
+ Copy
+ MaybeSerializeDeserialize
+ sp_std::fmt::Debug
+ Default
+ From<u64>
+ TypeInfo
+ MaxEncodedLen;
/// Origin required for auto-funding the deficit.
type FundOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The issuance to ignore. This is subtracted from the `Currency`'s `total_issuance` to get
/// the issuance with which we determine the thawed value of a given proportion.
type IgnoredIssuance: Get<BalanceOf<Self>>;
/// The accounting system for the fungible counterpart tokens.
type Counterpart: FungibleMutate<Self::AccountId>;
/// The system to convert an overall proportion of issuance into a number of fungible
/// counterpart tokens.
///
/// In general it's best to use `WithMaximumOf`.
type CounterpartAmount: ConvertBack<
Perquintill,
<Self::Counterpart as FungibleInspect<Self::AccountId>>::Balance,
>;
/// Unbalanced handler to account for funds created (in case of a higher total issuance over
/// freezing period).
type Deficit: OnUnbalanced<PositiveImbalanceOf<Self>>;
/// The target sum of all receipts' proportions.
type Target: Get<Perquintill>;
/// Number of duration queues in total. This sets the maximum duration supported, which is
/// this value multiplied by `Period`.
#[pallet::constant]
type QueueCount: Get<u32>;
/// Maximum number of items that may be in each duration queue.
///
/// Must be larger than zero.
#[pallet::constant]
type MaxQueueLen: Get<u32>;
/// Portion of the queue which is free from ordering and just a FIFO.
///
/// Must be no greater than `MaxQueueLen`.
#[pallet::constant]
type FifoQueueLen: Get<u32>;
/// The base period for the duration queues. This is the common multiple across all
/// supported freezing durations that can be bid upon.
#[pallet::constant]
type BasePeriod: Get<Self::BlockNumber>;
/// The minimum amount of funds that may be placed in a bid. Note that this
/// does not actually limit the amount which may be represented in a receipt since bids may
/// be split up by the system.
///
/// It should be at least big enough to ensure that there is no possible storage spam attack
/// or queue-filling attack.
#[pallet::constant]
type MinBid: Get<BalanceOf<Self>>;
/// The minimum amount of funds which may intentionally be left remaining under a single
/// receipt.
#[pallet::constant]
type MinReceipt: Get<Perquintill>;
/// The number of blocks between consecutive attempts to dequeue bids and create receipts.
///
/// A larger value results in fewer storage hits each block, but a slower period to get to
/// the target.
#[pallet::constant]
type IntakePeriod: Get<Self::BlockNumber>;
/// The maximum amount of bids that can consolidated into receipts in a single intake. A
/// larger value here means less of the block available for transactions should there be a
/// glut of bids.
#[pallet::constant]
type MaxIntakeWeight: Get<Weight>;
/// The maximum proportion which may be thawed and the period over which it is reset.
#[pallet::constant]
type ThawThrottle: Get<(Perquintill, Self::BlockNumber)>;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
/// A single bid, an item of a *queue* in `Queues`.
#[derive(
Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
pub struct Bid<Balance, AccountId> {
/// The amount bid.
pub amount: Balance,
/// The owner of the bid.
pub who: AccountId,
}
/// Information representing a receipt.
#[derive(
Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
pub struct ReceiptRecord<AccountId, BlockNumber> {
/// The proportion of the effective total issuance.
pub proportion: Perquintill,
/// The account to whom this receipt belongs.
pub who: AccountId,
/// The time after which this receipt can be thawed.
pub expiry: BlockNumber,
}
/// An index for a receipt.
pub type ReceiptIndex = u32;
/// Overall information package on the outstanding receipts.
///
/// The way of determining the net issuance (i.e. after factoring in all maturing frozen funds)
/// is:
///
/// `issuance - frozen + proportion * issuance`
///
/// where `issuance = total_issuance - IgnoredIssuance`
#[derive(
Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen,
)]
pub struct SummaryRecord<BlockNumber> {
/// The total proportion over all outstanding receipts.
pub proportion_owed: Perquintill,
/// The total number of receipts created so far.
pub index: ReceiptIndex,
/// The amount (as a proportion of ETI) which has been thawed in this period so far.
pub thawed: Perquintill,
/// The current thaw period's beginning.
pub last_period: BlockNumber,
}
pub struct OnEmptyQueueTotals<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<QueueTotalsTypeOf<T>> for OnEmptyQueueTotals<T> {
fn get() -> QueueTotalsTypeOf<T> {
BoundedVec::truncate_from(vec![
(0, Zero::zero());
<T as Config>::QueueCount::get() as usize
])
}
}
/// The totals of items and balances within each queue. Saves a lot of storage reads in the
/// case of sparsely packed queues.
///
/// The vector is indexed by duration in `Period`s, offset by one, so information on the queue
/// whose duration is one `Period` would be storage `0`.
#[pallet::storage]
pub type QueueTotals<T: Config> =
StorageValue<_, QueueTotalsTypeOf<T>, ValueQuery, OnEmptyQueueTotals<T>>;
/// The queues of bids. Indexed by duration (in `Period`s).
#[pallet::storage]
pub type Queues<T: Config> =
StorageMap<_, Blake2_128Concat, u32, BoundedVec<BidOf<T>, T::MaxQueueLen>, ValueQuery>;
/// Summary information over the general state.
#[pallet::storage]
pub type Summary<T> = StorageValue<_, SummaryRecordOf<T>, ValueQuery>;
/// The currently outstanding receipts, indexed according to the order of creation.
#[pallet::storage]
pub type Receipts<T> =
StorageMap<_, Blake2_128Concat, ReceiptIndex, ReceiptRecordOf<T>, OptionQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A bid was successfully placed.
BidPlaced { who: T::AccountId, amount: BalanceOf<T>, duration: u32 },
/// A bid was successfully removed (before being accepted).
BidRetracted { who: T::AccountId, amount: BalanceOf<T>, duration: u32 },
/// A bid was dropped from a queue because of another, more substantial, bid was present.
BidDropped { who: T::AccountId, amount: BalanceOf<T>, duration: u32 },
/// A bid was accepted. The balance may not be released until expiry.
Issued {
/// The identity of the receipt.
index: ReceiptIndex,
/// The block number at which the receipt may be thawed.
expiry: T::BlockNumber,
/// The owner of the receipt.
who: T::AccountId,
/// The proportion of the effective total issuance which the receipt represents.
proportion: Perquintill,
/// The amount of funds which were debited from the owner.
amount: BalanceOf<T>,
},
/// An receipt has been (at least partially) thawed.
Thawed {
/// The identity of the receipt.
index: ReceiptIndex,
/// The owner.
who: T::AccountId,
/// The proportion of the effective total issuance by which the owner was debited.
proportion: Perquintill,
/// The amount by which the owner was credited.
amount: BalanceOf<T>,
/// If `true` then the receipt is done.
dropped: bool,
},
/// An automatic funding of the deficit was made.
Funded { deficit: BalanceOf<T> },
/// A receipt was transfered.
Transferred { from: T::AccountId, to: T::AccountId, index: ReceiptIndex },
}
#[pallet::error]
pub enum Error<T> {
/// The duration of the bid is less than one.
DurationTooSmall,
/// The duration is the bid is greater than the number of queues.
DurationTooBig,
/// The amount of the bid is less than the minimum allowed.
AmountTooSmall,
/// The queue for the bid's duration is full and the amount bid is too low to get in
/// through replacing an existing bid.
BidTooLow,
/// Bond index is unknown.
Unknown,
/// Not the owner of the receipt.
NotOwner,
/// Bond not yet at expiry date.
NotExpired,
/// The given bid for retraction is not found.
NotFound,
/// The portion supplied is beyond the value of the receipt.
TooMuch,
/// Not enough funds are held to pay out.
Unfunded,
/// There are enough funds for what is required.
Funded,
/// The thaw throttle has been reached for this period.
Throttled,
/// The operation would result in a receipt worth an insignficant value.
MakesDust,
}
pub(crate) struct WeightCounter {
pub(crate) used: Weight,
pub(crate) limit: Weight,
}
impl WeightCounter {
#[allow(dead_code)]
pub(crate) fn unlimited() -> Self {
WeightCounter { used: Weight::zero(), limit: Weight::max_value() }
}
fn check_accrue(&mut self, w: Weight) -> bool {
let test = self.used.saturating_add(w);
if test.any_gt(self.limit) {
false
} else {
self.used = test;
true
}
}
fn can_accrue(&mut self, w: Weight) -> bool {
self.used.saturating_add(w).all_lte(self.limit)
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight {
let mut weight_counter =
WeightCounter { used: Weight::zero(), limit: T::MaxIntakeWeight::get() };
if T::IntakePeriod::get().is_zero() || (n % T::IntakePeriod::get()).is_zero() {
if weight_counter.check_accrue(T::WeightInfo::process_queues()) {
Self::process_queues(
T::Target::get(),
T::QueueCount::get(),
u32::max_value(),
&mut weight_counter,
)
}
}
weight_counter.used
}
fn integrity_test() {
assert!(!T::IntakePeriod::get().is_zero());
assert!(!T::MaxQueueLen::get().is_zero());
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Place a bid.
///
/// Origin must be Signed, and account must have at least `amount` in free balance.
///
/// - `amount`: The amount of the bid; these funds will be reserved, and if/when
/// consolidated, removed. Must be at least `MinBid`.
/// - `duration`: The number of periods before which the newly consolidated bid may be
/// thawed. Must be greater than 1 and no more than `QueueCount`.
///
/// Complexities:
/// - `Queues[duration].len()` (just take max).
#[pallet::weight(T::WeightInfo::place_bid_max())]
pub fn place_bid(
origin: OriginFor<T>,
#[pallet::compact] amount: BalanceOf<T>,
duration: u32,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(amount >= T::MinBid::get(), Error::<T>::AmountTooSmall);
let queue_count = T::QueueCount::get() as usize;
let queue_index = duration.checked_sub(1).ok_or(Error::<T>::DurationTooSmall)? as usize;
ensure!(queue_index < queue_count, Error::<T>::DurationTooBig);
let net = Queues::<T>::try_mutate(
duration,
|q| -> Result<(u32, BalanceOf<T>), DispatchError> {
let queue_full = q.len() == T::MaxQueueLen::get() as usize;
ensure!(!queue_full || q[0].amount < amount, Error::<T>::BidTooLow);
T::Currency::reserve(&who, amount)?;
// queue is <Ordered: Lowest ... Highest><Fifo: Last ... First>
let mut bid = Bid { amount, who: who.clone() };
let net = if queue_full {
sp_std::mem::swap(&mut q[0], &mut bid);
let _ = T::Currency::unreserve(&bid.who, bid.amount);
Self::deposit_event(Event::<T>::BidDropped {
who: bid.who,
amount: bid.amount,
duration,
});
(0, amount - bid.amount)
} else {
q.try_insert(0, bid).expect("verified queue was not full above. qed.");
(1, amount)
};
let sorted_item_count = q.len().saturating_sub(T::FifoQueueLen::get() as usize);
if sorted_item_count > 1 {
q[0..sorted_item_count].sort_by_key(|x| x.amount);
}
Ok(net)
},
)?;
QueueTotals::<T>::mutate(|qs| {
qs.bounded_resize(queue_count, (0, Zero::zero()));
qs[queue_index].0 += net.0;
qs[queue_index].1.saturating_accrue(net.1);
});
Self::deposit_event(Event::BidPlaced { who, amount, duration });
Ok(())
}
/// Retract a previously placed bid.
///
/// Origin must be Signed, and the account should have previously issued a still-active bid
/// of `amount` for `duration`.
///
/// - `amount`: The amount of the previous bid.
/// - `duration`: The duration of the previous bid.
#[pallet::weight(T::WeightInfo::retract_bid(T::MaxQueueLen::get()))]
pub fn retract_bid(
origin: OriginFor<T>,
#[pallet::compact] amount: BalanceOf<T>,
duration: u32,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let queue_count = T::QueueCount::get() as usize;
let queue_index = duration.checked_sub(1).ok_or(Error::<T>::DurationTooSmall)? as usize;
ensure!(queue_index < queue_count, Error::<T>::DurationTooBig);
let bid = Bid { amount, who };
let new_len = Queues::<T>::try_mutate(duration, |q| -> Result<u32, DispatchError> {
let pos = q.iter().position(|i| i == &bid).ok_or(Error::<T>::NotFound)?;
q.remove(pos);
Ok(q.len() as u32)
})?;
QueueTotals::<T>::mutate(|qs| {
qs.bounded_resize(queue_count, (0, Zero::zero()));
qs[queue_index].0 = new_len;
qs[queue_index].1.saturating_reduce(bid.amount);
});
T::Currency::unreserve(&bid.who, bid.amount);
Self::deposit_event(Event::BidRetracted { who: bid.who, amount: bid.amount, duration });
Ok(())
}
/// Ensure we have sufficient funding for all potential payouts.
///
/// - `origin`: Must be accepted by `FundOrigin`.
#[pallet::weight(T::WeightInfo::fund_deficit())]
pub fn fund_deficit(origin: OriginFor<T>) -> DispatchResult {
T::FundOrigin::ensure_origin(origin)?;
let summary: SummaryRecordOf<T> = Summary::<T>::get();
let our_account = Self::account_id();
let issuance = Self::issuance_with(&our_account, &summary);
let deficit = issuance.required.saturating_sub(issuance.holdings);
ensure!(!deficit.is_zero(), Error::<T>::Funded);
T::Deficit::on_unbalanced(T::Currency::deposit_creating(&our_account, deficit));
Self::deposit_event(Event::<T>::Funded { deficit });
Ok(())
}
/// Reduce or remove an outstanding receipt, placing the according proportion of funds into
/// the account of the owner.
///
/// - `origin`: Must be Signed and the account must be the owner of the receipt `index` as
/// well as any fungible counterpart.
/// - `index`: The index of the receipt.
/// - `portion`: If `Some`, then only the given portion of the receipt should be thawed. If
/// `None`, then all of it should be.
#[pallet::weight(T::WeightInfo::thaw())]
pub fn thaw(
origin: OriginFor<T>,
#[pallet::compact] index: ReceiptIndex,
portion: Option<<T::Counterpart as FungibleInspect<T::AccountId>>::Balance>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Look for `index`
let mut receipt: ReceiptRecordOf<T> =
Receipts::<T>::get(index).ok_or(Error::<T>::Unknown)?;
// If found, check the owner is `who`.
ensure!(receipt.who == who, Error::<T>::NotOwner);
let now = frame_system::Pallet::<T>::block_number();
ensure!(now >= receipt.expiry, Error::<T>::NotExpired);
let mut summary: SummaryRecordOf<T> = Summary::<T>::get();
let proportion = if let Some(counterpart) = portion {
let proportion = T::CounterpartAmount::convert_back(counterpart);
ensure!(proportion <= receipt.proportion, Error::<T>::TooMuch);
let remaining = receipt.proportion.saturating_sub(proportion);
ensure!(
remaining.is_zero() || remaining >= T::MinReceipt::get(),
Error::<T>::MakesDust
);
proportion
} else {
receipt.proportion
};
let (throttle, throttle_period) = T::ThawThrottle::get();
if now.saturating_sub(summary.last_period) >= throttle_period {
summary.thawed = Zero::zero();
summary.last_period = now;
}
summary.thawed.saturating_accrue(proportion);
ensure!(summary.thawed <= throttle, Error::<T>::Throttled);
T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(proportion))?;
// Multiply the proportion it is by the total issued.
let our_account = Self::account_id();
let effective_issuance = Self::issuance_with(&our_account, &summary).effective;
let amount = proportion * effective_issuance;
receipt.proportion.saturating_reduce(proportion);
summary.proportion_owed.saturating_reduce(proportion);
T::Currency::transfer(&our_account, &who, amount, AllowDeath)
.map_err(|_| Error::<T>::Unfunded)?;
let dropped = receipt.proportion.is_zero();
if dropped {
Receipts::<T>::remove(index);
} else {
Receipts::<T>::insert(index, &receipt);
}
Summary::<T>::put(&summary);
Self::deposit_event(Event::Thawed { index, who, amount, proportion, dropped });
Ok(())
}
}
/// Issuance information returned by `issuance()`.
#[derive(RuntimeDebug)]
pub struct IssuanceInfo<Balance> {
/// The balance held in reserve by this pallet instance.
pub holdings: Balance,
/// The (non-ignored) issuance in the system, not including this pallet's account.
pub other: Balance,
/// The effective total issuance, hypothetically if all outstanding receipts were thawed at
/// present.
pub effective: Balance,
/// The amount needed to be the pallet instance's account in case all outstanding receipts
/// were thawed at present.
pub required: Balance,
}
impl<T: Config> NonfungibleInspect<T::AccountId> for Pallet<T> {
type ItemId = ReceiptIndex;
fn owner(item: &ReceiptIndex) -> Option<T::AccountId> {
Receipts::<T>::get(item).map(|r| r.who)
}
fn attribute(item: &Self::ItemId, key: &[u8]) -> Option<Vec<u8>> {
let item = Receipts::<T>::get(item)?;
match key {
b"proportion" => Some(item.proportion.encode()),
b"expiry" => Some(item.expiry.encode()),
_ => None,
}
}
}
impl<T: Config> NonfungibleTransfer<T::AccountId> for Pallet<T> {
fn transfer(index: &ReceiptIndex, destination: &T::AccountId) -> DispatchResult {
let mut item = Receipts::<T>::get(index).ok_or(TokenError::UnknownAsset)?;
let from = item.who;
item.who = destination.clone();
Receipts::<T>::insert(&index, &item);
Pallet::<T>::deposit_event(Event::<T>::Transferred {
from,
to: item.who,
index: *index,
});
Ok(())
}
}
impl<T: Config> Pallet<T> {
/// The account ID of the reserves.
///
/// 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()
}
/// Returns information on the issuance within the system.
pub fn issuance() -> IssuanceInfo<BalanceOf<T>> {
Self::issuance_with(&Self::account_id(), &Summary::<T>::get())
}
/// Returns information on the issuance within the system
///
/// This function is equivalent to `issuance`, except that it accepts arguments rather than
/// queries state. If the arguments are already known, then this may be slightly more
/// performant.
///
/// - `our_account`: The value returned by `Self::account_id()`.
/// - `summary`: The value returned by `Summary::<T>::get()`.
pub fn issuance_with(
our_account: &T::AccountId,
summary: &SummaryRecordOf<T>,
) -> IssuanceInfo<BalanceOf<T>> {
let total_issuance =
T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get());
let holdings = T::Currency::free_balance(our_account);
let other = total_issuance.saturating_sub(holdings);
let effective =
summary.proportion_owed.left_from_one().saturating_reciprocal_mul(other);
let required = summary.proportion_owed * effective;
IssuanceInfo { holdings, other, effective, required }
}
/// Process some bids into receipts up to a `target` total of all receipts.
///
/// Touch at most `max_queues`.
///
/// Return the weight used.
pub(crate) fn process_queues(
target: Perquintill,
max_queues: u32,
max_bids: u32,
weight: &mut WeightCounter,
) {
let mut summary: SummaryRecordOf<T> = Summary::<T>::get();
if summary.proportion_owed >= target {
return
}
let now = frame_system::Pallet::<T>::block_number();
let our_account = Self::account_id();
let issuance: IssuanceInfoOf<T> = Self::issuance_with(&our_account, &summary);
let mut remaining = target.saturating_sub(summary.proportion_owed) * issuance.effective;
let mut queues_hit = 0;
let mut bids_hit = 0;
let mut totals = QueueTotals::<T>::get();
let queue_count = T::QueueCount::get();
totals.bounded_resize(queue_count as usize, (0, Zero::zero()));
for duration in (1..=queue_count).rev() {
if totals[duration as usize - 1].0.is_zero() {
continue
}
if remaining.is_zero() || queues_hit >= max_queues
|| !weight.check_accrue(T::WeightInfo::process_queue())
// No point trying to process a queue if we can't process a single bid.
|| !weight.can_accrue(T::WeightInfo::process_bid())
{
break
}
let b = Self::process_queue(
duration,
now,
&our_account,
&issuance,
max_bids.saturating_sub(bids_hit),
&mut remaining,
&mut totals[duration as usize - 1],
&mut summary,
weight,
);
bids_hit.saturating_accrue(b);
queues_hit.saturating_inc();
}
QueueTotals::<T>::put(&totals);
Summary::<T>::put(&summary);
}
pub(crate) fn process_queue(
duration: u32,
now: T::BlockNumber,
our_account: &T::AccountId,
issuance: &IssuanceInfo<BalanceOf<T>>,
max_bids: u32,
remaining: &mut BalanceOf<T>,
queue_total: &mut (u32, BalanceOf<T>),
summary: &mut SummaryRecordOf<T>,
weight: &mut WeightCounter,
) -> u32 {
let mut queue: BoundedVec<BidOf<T>, _> = Queues::<T>::get(&duration);
let expiry = now.saturating_add(T::BasePeriod::get().saturating_mul(duration.into()));
let mut count = 0;
while count < max_bids &&
!queue.is_empty() &&
!remaining.is_zero() &&
weight.check_accrue(T::WeightInfo::process_bid())
{
let bid = match queue.pop() {
Some(b) => b,
None => break,
};
if let Some(bid) = Self::process_bid(
bid,
expiry,
our_account,
issuance,
remaining,
&mut queue_total.1,
summary,
) {
queue.try_push(bid).expect("just popped, so there must be space. qed");
// This should exit at the next iteration (though nothing will break if it
// doesn't).
}
count.saturating_inc();
}
queue_total.0 = queue.len() as u32;
Queues::<T>::insert(&duration, &queue);
count
}
pub(crate) fn process_bid(
mut bid: BidOf<T>,
expiry: T::BlockNumber,
our_account: &T::AccountId,
issuance: &IssuanceInfo<BalanceOf<T>>,
remaining: &mut BalanceOf<T>,
queue_amount: &mut BalanceOf<T>,
summary: &mut SummaryRecordOf<T>,
) -> Option<BidOf<T>> {
let result = if *remaining < bid.amount {
let overflow = bid.amount - *remaining;
bid.amount = *remaining;
Some(Bid { amount: overflow, who: bid.who.clone() })
} else {
None
};
let amount = bid.amount.saturating_sub(T::Currency::unreserve(&bid.who, bid.amount));
if T::Currency::transfer(&bid.who, &our_account, amount, AllowDeath).is_err() {
return result
}
// Can never overflow due to block above.
remaining.saturating_reduce(amount);
// Should never underflow since it should track the total of the
// bids exactly, but we'll be defensive.
queue_amount.defensive_saturating_reduce(amount);
// Now to activate the bid...
let n = amount;
let d = issuance.effective;
let proportion = Perquintill::from_rational(n, d);
let who = bid.who;
let index = summary.index;
summary.proportion_owed.defensive_saturating_accrue(proportion);
summary.index += 1;
let e = Event::Issued { index, expiry, who: who.clone(), amount, proportion };
Self::deposit_event(e);
let receipt = ReceiptRecord { proportion, who: who.clone(), expiry };
Receipts::<T>::insert(index, receipt);
// issue the fungible counterpart
let fung_eq = T::CounterpartAmount::convert(proportion);
let _ = T::Counterpart::mint_into(&who, fung_eq).defensive();
result
}
}
}
+166
View File
@@ -0,0 +1,166 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! Test environment for NIS pallet.
use crate::{self as pallet_nis, Perquintill, WithMaximumOf};
use frame_support::{
ord_parameter_types, parameter_types,
traits::{ConstU16, ConstU32, ConstU64, Currency, OnFinalize, OnInitialize, StorageMapShim},
weights::Weight,
PalletId,
};
use pallet_balances::{Instance1, Instance2};
use sp_core::{ConstU128, H256};
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system,
Balances: pallet_balances::<Instance1>,
NisBalances: pallet_balances::<Instance2>,
Nis: pallet_nis,
}
);
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ConstU16<42>;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
impl pallet_balances::Config<Instance1> for Test {
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = frame_support::traits::ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
}
impl pallet_balances::Config<Instance2> for Test {
type Balance = u128;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = frame_support::traits::ConstU128<1>;
type AccountStore = StorageMapShim<
pallet_balances::Account<Test, Instance2>,
frame_system::Provider<Test>,
u64,
pallet_balances::AccountData<u128>,
>;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
}
parameter_types! {
pub IgnoredIssuance: u64 = Balances::total_balance(&0); // Account zero is ignored.
pub const NisPalletId: PalletId = PalletId(*b"py/nis ");
pub static Target: Perquintill = Perquintill::zero();
pub const MinReceipt: Perquintill = Perquintill::from_percent(1);
pub const ThawThrottle: (Perquintill, u64) = (Perquintill::from_percent(25), 5);
pub static MaxIntakeWeight: Weight = Weight::from_ref_time(2_000_000_000_000);
}
ord_parameter_types! {
pub const One: u64 = 1;
}
impl pallet_nis::Config for Test {
type WeightInfo = ();
type RuntimeEvent = RuntimeEvent;
type PalletId = NisPalletId;
type Currency = Balances;
type CurrencyBalance = <Self as pallet_balances::Config<Instance1>>::Balance;
type FundOrigin = frame_system::EnsureSigned<Self::AccountId>;
type Deficit = ();
type IgnoredIssuance = IgnoredIssuance;
type Counterpart = NisBalances;
type CounterpartAmount = WithMaximumOf<ConstU128<21_000_000u128>>;
type Target = Target;
type QueueCount = ConstU32<3>;
type MaxQueueLen = ConstU32<3>;
type FifoQueueLen = ConstU32<1>;
type BasePeriod = ConstU64<3>;
type MinBid = ConstU64<2>;
type IntakePeriod = ConstU64<2>;
type MaxIntakeWeight = MaxIntakeWeight;
type MinReceipt = MinReceipt;
type ThawThrottle = ThawThrottle;
}
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
pallet_balances::GenesisConfig::<Test, Instance1> {
balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)],
}
.assimilate_storage(&mut t)
.unwrap();
t.into()
}
pub fn run_to_block(n: u64) {
while System::block_number() < n {
Nis::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Balances::on_initialize(System::block_number());
Nis::on_initialize(System::block_number());
}
}
+654
View File
@@ -0,0 +1,654 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! Tests for NIS pallet.
use super::*;
use crate::{mock::*, Error};
use frame_support::{
assert_noop, assert_ok,
traits::{
nonfungible::{Inspect, Transfer},
Currency,
},
};
use pallet_balances::{Error as BalancesError, Instance1};
use sp_arithmetic::Perquintill;
use sp_runtime::{Saturating, TokenError};
fn pot() -> u64 {
Balances::free_balance(&Nis::account_id())
}
fn enlarge(amount: u64, max_bids: u32) {
let summary: SummaryRecord<u64> = Summary::<Test>::get();
let increase_in_proportion_owed = Perquintill::from_rational(amount, Nis::issuance().effective);
let target = summary.proportion_owed.saturating_add(increase_in_proportion_owed);
Nis::process_queues(target, u32::max_value(), max_bids, &mut WeightCounter::unlimited());
}
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
for q in 0..3 {
assert!(Queues::<Test>::get(q).is_empty());
}
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::zero(),
index: 0,
last_period: 0,
thawed: Perquintill::zero()
}
);
});
}
#[test]
fn place_bid_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 1, 2), Error::<Test>::AmountTooSmall);
assert_noop!(
Nis::place_bid(RuntimeOrigin::signed(1), 101, 2),
BalancesError::<Test, Instance1>::InsufficientBalance
);
assert_noop!(
Nis::place_bid(RuntimeOrigin::signed(1), 10, 4),
Error::<Test>::DurationTooBig
);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_eq!(Balances::reserved_balance(1), 10);
assert_eq!(Queues::<Test>::get(2), vec![Bid { amount: 10, who: 1 }]);
assert_eq!(QueueTotals::<Test>::get(), vec![(0, 0), (1, 10), (0, 0)]);
});
}
#[test]
fn place_bid_queuing_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 20, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 5, 2));
assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 5, 2), Error::<Test>::BidTooLow);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 15, 2));
assert_eq!(Balances::reserved_balance(1), 45);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 25, 2));
assert_eq!(Balances::reserved_balance(1), 60);
assert_noop!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2), Error::<Test>::BidTooLow);
assert_eq!(
Queues::<Test>::get(2),
vec![
Bid { amount: 15, who: 1 },
Bid { amount: 25, who: 1 },
Bid { amount: 20, who: 1 },
]
);
assert_eq!(QueueTotals::<Test>::get(), vec![(0, 0), (3, 60), (0, 0)]);
});
}
#[test]
fn place_bid_fails_when_queue_full() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 10, 2));
assert_noop!(Nis::place_bid(RuntimeOrigin::signed(4), 10, 2), Error::<Test>::BidTooLow);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(4), 10, 3));
});
}
#[test]
fn multiple_place_bids_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 3));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2));
assert_eq!(Balances::reserved_balance(1), 40);
assert_eq!(Balances::reserved_balance(2), 10);
assert_eq!(Queues::<Test>::get(1), vec![Bid { amount: 10, who: 1 },]);
assert_eq!(
Queues::<Test>::get(2),
vec![
Bid { amount: 10, who: 2 },
Bid { amount: 10, who: 1 },
Bid { amount: 10, who: 1 },
]
);
assert_eq!(Queues::<Test>::get(3), vec![Bid { amount: 10, who: 1 },]);
assert_eq!(QueueTotals::<Test>::get(), vec![(1, 10), (3, 30), (1, 10)]);
});
}
#[test]
fn retract_single_item_queue_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_ok!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 1));
assert_eq!(Balances::reserved_balance(1), 10);
assert_eq!(Queues::<Test>::get(1), vec![]);
assert_eq!(Queues::<Test>::get(2), vec![Bid { amount: 10, who: 1 }]);
assert_eq!(QueueTotals::<Test>::get(), vec![(0, 0), (1, 10), (0, 0)]);
});
}
#[test]
fn retract_with_other_and_duplicate_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 10, 2));
assert_ok!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 2));
assert_eq!(Balances::reserved_balance(1), 20);
assert_eq!(Balances::reserved_balance(2), 10);
assert_eq!(Queues::<Test>::get(1), vec![Bid { amount: 10, who: 1 },]);
assert_eq!(
Queues::<Test>::get(2),
vec![Bid { amount: 10, who: 2 }, Bid { amount: 10, who: 1 },]
);
assert_eq!(QueueTotals::<Test>::get(), vec![(1, 10), (2, 20), (0, 0)]);
});
}
#[test]
fn retract_non_existent_item_fails() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 1), Error::<Test>::NotFound);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 10, 1));
assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 20, 1), Error::<Test>::NotFound);
assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(1), 10, 2), Error::<Test>::NotFound);
assert_noop!(Nis::retract_bid(RuntimeOrigin::signed(2), 10, 1), Error::<Test>::NotFound);
});
}
#[test]
fn basic_enlarge_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2));
enlarge(40, 2);
// Takes 2/2, then stopped because it reaches its max amount
assert_eq!(Balances::reserved_balance(1), 40);
assert_eq!(Balances::reserved_balance(2), 0);
assert_eq!(pot(), 40);
assert_eq!(Queues::<Test>::get(1), vec![Bid { amount: 40, who: 1 }]);
assert_eq!(Queues::<Test>::get(2), vec![]);
assert_eq!(QueueTotals::<Test>::get(), vec![(1, 40), (0, 0), (0, 0)]);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(10),
index: 1,
last_period: 0,
thawed: Perquintill::zero()
}
);
assert_eq!(
Receipts::<Test>::get(0).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 7 }
);
});
}
#[test]
fn enlarge_respects_bids_limit() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 40, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(4), 40, 3));
enlarge(100, 2);
// Should have taken 4/3 and 2/2, then stopped because it's only allowed 2.
assert_eq!(Queues::<Test>::get(1), vec![Bid { amount: 40, who: 1 }]);
assert_eq!(Queues::<Test>::get(2), vec![Bid { amount: 40, who: 3 }]);
assert_eq!(Queues::<Test>::get(3), vec![]);
assert_eq!(QueueTotals::<Test>::get(), vec![(1, 40), (1, 40), (0, 0)]);
assert_eq!(
Receipts::<Test>::get(0).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 4, expiry: 10 }
);
assert_eq!(
Receipts::<Test>::get(1).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 7 }
);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(20),
index: 2,
last_period: 0,
thawed: Perquintill::zero()
}
);
});
}
#[test]
fn enlarge_respects_amount_limit_and_will_split() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 80, 1));
enlarge(40, 2);
// Takes 2/2, then stopped because it reaches its max amount
assert_eq!(Queues::<Test>::get(1), vec![Bid { amount: 40, who: 1 }]);
assert_eq!(QueueTotals::<Test>::get(), vec![(1, 40), (0, 0), (0, 0)]);
assert_eq!(
Receipts::<Test>::get(0).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 4 }
);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(10),
index: 1,
last_period: 0,
thawed: Perquintill::zero()
}
);
});
}
#[test]
fn basic_thaw_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1));
assert_eq!(Nis::issuance().effective, 400);
assert_eq!(Balances::free_balance(1), 60);
assert_eq!(Balances::reserved_balance(1), 40);
assert_eq!(pot(), 0);
enlarge(40, 1);
assert_eq!(Nis::issuance().effective, 400);
assert_eq!(Balances::free_balance(1), 60);
assert_eq!(Balances::reserved_balance(1), 0);
assert_eq!(pot(), 40);
run_to_block(3);
assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::<Test>::NotExpired);
run_to_block(4);
assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 1, None), Error::<Test>::Unknown);
assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 0, None), Error::<Test>::NotOwner);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None));
assert_eq!(NisBalances::free_balance(1), 0);
assert_eq!(Nis::typed_attribute::<_, Perquintill>(&0, b"proportion"), None);
assert_eq!(Nis::issuance().effective, 400);
assert_eq!(Balances::free_balance(1), 100);
assert_eq!(pot(), 0);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::zero(),
index: 1,
last_period: 0,
thawed: Perquintill::from_percent(10)
}
);
assert_eq!(Receipts::<Test>::get(0), None);
});
}
#[test]
fn partial_thaw_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 80, 1));
enlarge(80, 1);
assert_eq!(pot(), 80);
run_to_block(4);
assert_noop!(
Nis::thaw(RuntimeOrigin::signed(1), 0, Some(4_100_000)),
Error::<Test>::MakesDust
);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, Some(1_050_000)));
assert_eq!(NisBalances::free_balance(1), 3_150_000);
assert_eq!(
Nis::typed_attribute::<_, Perquintill>(&0, b"proportion"),
Some(Perquintill::from_rational(3_150_000u64, 21_000_000u64)),
);
assert_eq!(Nis::issuance().effective, 400);
assert_eq!(Balances::free_balance(1), 40);
assert_eq!(pot(), 60);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None));
assert_eq!(Nis::issuance().effective, 400);
assert_eq!(Balances::free_balance(1), 100);
assert_eq!(pot(), 0);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::zero(),
index: 1,
last_period: 0,
thawed: Perquintill::from_percent(20)
}
);
assert_eq!(Receipts::<Test>::get(0), None);
});
}
#[test]
fn thaw_respects_transfers() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1));
enlarge(40, 1);
run_to_block(4);
assert_eq!(Nis::owner(&0), Some(1));
assert_ok!(Nis::transfer(&0, &2));
// Transfering the receipt...
assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::<Test>::NotOwner);
// ...can't be thawed due to missing counterpart
assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 0, None), TokenError::NoFunds);
// Transfer the counterpart also...
assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(1), 2, 2100000));
// ...and thawing is possible.
assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 0, None));
assert_eq!(Balances::free_balance(2), 140);
assert_eq!(Balances::free_balance(1), 60);
});
}
#[test]
fn thaw_when_issuance_higher_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1));
enlarge(100, 1);
assert_eq!(NisBalances::free_balance(1), 5_250_000); // (25% of 21m)
// Everybody else's balances goes up by 50%
Balances::make_free_balance_be(&2, 150);
Balances::make_free_balance_be(&3, 150);
Balances::make_free_balance_be(&4, 150);
run_to_block(4);
// Unfunded initially...
assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::<Test>::Unfunded);
// ...so we fund.
assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1)));
// Transfer counterpart away...
assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(1), 2, 250_000));
// ...and it's not thawable.
assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), TokenError::NoFunds);
// Transfer counterpart back...
assert_ok!(NisBalances::transfer(RuntimeOrigin::signed(2), 1, 250_000));
// ...and it is.
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None));
assert_eq!(Balances::free_balance(1), 150);
assert_eq!(Balances::reserved_balance(1), 0);
});
}
#[test]
fn thaw_with_ignored_issuance_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
// Give account zero some balance.
Balances::make_free_balance_be(&0, 200);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1));
enlarge(100, 1);
// Account zero transfers 50 into everyone else's accounts.
assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 2, 50));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 3, 50));
assert_ok!(Balances::transfer(RuntimeOrigin::signed(0), 4, 50));
run_to_block(4);
// Unfunded initially...
assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 0, None), Error::<Test>::Unfunded);
// ...so we fund...
assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1)));
// ...and then it's ok.
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None));
// Account zero changes have been ignored.
assert_eq!(Balances::free_balance(1), 150);
assert_eq!(Balances::reserved_balance(1), 0);
});
}
#[test]
fn thaw_when_issuance_lower_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 100, 1));
enlarge(100, 1);
// Everybody else's balances goes down by 25%
Balances::make_free_balance_be(&2, 75);
Balances::make_free_balance_be(&3, 75);
Balances::make_free_balance_be(&4, 75);
run_to_block(4);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None));
assert_eq!(Balances::free_balance(1), 75);
assert_eq!(Balances::reserved_balance(1), 0);
});
}
#[test]
fn multiple_thaws_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 60, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 50, 1));
enlarge(200, 3);
// Double everyone's free balances.
Balances::make_free_balance_be(&2, 100);
Balances::make_free_balance_be(&3, 200);
Balances::make_free_balance_be(&4, 200);
assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1)));
run_to_block(4);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None));
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 1, None));
assert_noop!(Nis::thaw(RuntimeOrigin::signed(2), 2, None), Error::<Test>::Throttled);
run_to_block(5);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 2, None));
assert_eq!(Balances::free_balance(1), 200);
assert_eq!(Balances::free_balance(2), 200);
});
}
#[test]
fn multiple_thaws_works_in_alternative_thaw_order() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 60, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 50, 1));
enlarge(200, 3);
// Double everyone's free balances.
Balances::make_free_balance_be(&2, 100);
Balances::make_free_balance_be(&3, 200);
Balances::make_free_balance_be(&4, 200);
assert_ok!(Nis::fund_deficit(RuntimeOrigin::signed(1)));
run_to_block(4);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(2), 2, None));
assert_noop!(Nis::thaw(RuntimeOrigin::signed(1), 1, None), Error::<Test>::Throttled);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 0, None));
run_to_block(5);
assert_ok!(Nis::thaw(RuntimeOrigin::signed(1), 1, None));
assert_eq!(Balances::free_balance(1), 200);
assert_eq!(Balances::free_balance(2), 200);
});
}
#[test]
fn enlargement_to_target_works() {
new_test_ext().execute_with(|| {
run_to_block(2);
let w = <() as WeightInfo>::process_queues() +
<() as WeightInfo>::process_queue() +
(<() as WeightInfo>::process_bid() * 2);
super::mock::MaxIntakeWeight::set(w);
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 1));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(1), 40, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 2));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(2), 40, 3));
assert_ok!(Nis::place_bid(RuntimeOrigin::signed(3), 40, 3));
Target::set(Perquintill::from_percent(40));
run_to_block(3);
assert_eq!(Queues::<Test>::get(1), vec![Bid { amount: 40, who: 1 },]);
assert_eq!(
Queues::<Test>::get(2),
vec![Bid { amount: 40, who: 2 }, Bid { amount: 40, who: 1 },]
);
assert_eq!(
Queues::<Test>::get(3),
vec![Bid { amount: 40, who: 3 }, Bid { amount: 40, who: 2 },]
);
assert_eq!(QueueTotals::<Test>::get(), vec![(1, 40), (2, 80), (2, 80)]);
run_to_block(4);
// Two new items should have been issued to 2 & 3 for 40 each & duration of 3.
assert_eq!(
Receipts::<Test>::get(0).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 13 }
);
assert_eq!(
Receipts::<Test>::get(1).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 3, expiry: 13 }
);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(20),
index: 2,
last_period: 0,
thawed: Perquintill::zero(),
}
);
run_to_block(5);
// No change
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(20),
index: 2,
last_period: 0,
thawed: Perquintill::zero()
}
);
run_to_block(6);
// Two new items should have been issued to 1 & 2 for 40 each & duration of 2.
assert_eq!(
Receipts::<Test>::get(2).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 12 }
);
assert_eq!(
Receipts::<Test>::get(3).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 2, expiry: 12 }
);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(40),
index: 4,
last_period: 0,
thawed: Perquintill::zero()
}
);
run_to_block(8);
// No change now.
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(40),
index: 4,
last_period: 0,
thawed: Perquintill::zero()
}
);
// Set target a bit higher to use up the remaining bid.
Target::set(Perquintill::from_percent(60));
run_to_block(10);
// Two new items should have been issued to 1 & 2 for 40 each & duration of 2.
assert_eq!(
Receipts::<Test>::get(4).unwrap(),
ReceiptRecord { proportion: Perquintill::from_percent(10), who: 1, expiry: 13 }
);
assert_eq!(
Summary::<Test>::get(),
SummaryRecord {
proportion_owed: Perquintill::from_percent(50),
index: 5,
last_period: 0,
thawed: Perquintill::zero()
}
);
});
}
+201
View File
@@ -0,0 +1,201 @@
// This file is part of Substrate.
// Copyright (C) 2022 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_nis
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// ./target/production/substrate
// benchmark
// pallet
// --chain=dev
// --steps=50
// --repeat=20
// --pallet=pallet_nis
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --template=./.maintain/frame-weight-template.hbs
// --output=./frame/nis/src/weights.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_nis.
pub trait WeightInfo {
fn place_bid(l: u32, ) -> Weight;
fn place_bid_max() -> Weight;
fn retract_bid(l: u32, ) -> Weight;
fn thaw() -> Weight;
fn fund_deficit() -> Weight;
fn process_queues() -> Weight;
fn process_queue() -> Weight;
fn process_bid() -> Weight;
}
/// Weights for pallet_nis using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
fn place_bid(l: u32, ) -> Weight {
// Minimum execution time: 42_332 nanoseconds.
Weight::from_ref_time(45_584_514 as u64)
// Standard Error: 129
.saturating_add(Weight::from_ref_time(45_727 as u64).saturating_mul(l as u64))
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
fn place_bid_max() -> Weight {
// Minimum execution time: 85_866 nanoseconds.
Weight::from_ref_time(87_171_000 as u64)
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
fn retract_bid(l: u32, ) -> Weight {
// Minimum execution time: 44_605 nanoseconds.
Weight::from_ref_time(46_850_108 as u64)
// Standard Error: 135
.saturating_add(Weight::from_ref_time(34_178 as u64).saturating_mul(l as u64))
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
// Storage: Nis Active (r:1 w:1)
// Storage: Nis ActiveTotal (r:1 w:1)
fn thaw() -> Weight {
// Minimum execution time: 55_143 nanoseconds.
Weight::from_ref_time(55_845_000 as u64)
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
// Storage: Nis Active (r:1 w:1)
// Storage: Nis ActiveTotal (r:1 w:1)
fn fund_deficit() -> Weight {
Weight::from_ref_time(47_753_000 as u64)
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
// Storage: Nis ActiveTotal (r:1 w:0)
fn process_queues() -> Weight {
Weight::from_ref_time(1_663_000 as u64)
.saturating_add(T::DbWeight::get().reads(1 as u64))
}
// Storage: Nis ActiveTotal (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis Active (r:0 w:1)
fn process_queue() -> Weight {
Weight::from_ref_time(40_797_000 as u64)
// Standard Error: 1_000
.saturating_add(T::DbWeight::get().reads(3 as u64))
.saturating_add(T::DbWeight::get().writes(3 as u64))
}
// Storage: Nis ActiveTotal (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis Active (r:0 w:1)
fn process_bid() -> Weight {
Weight::from_ref_time(14_944_000 as u64)
// Standard Error: 6_000
.saturating_add(T::DbWeight::get().reads(2 as u64))
.saturating_add(T::DbWeight::get().writes(2 as u64))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
fn place_bid(l: u32, ) -> Weight {
// Minimum execution time: 42_332 nanoseconds.
Weight::from_ref_time(45_584_514 as u64)
// Standard Error: 129
.saturating_add(Weight::from_ref_time(45_727 as u64).saturating_mul(l as u64))
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
fn place_bid_max() -> Weight {
// Minimum execution time: 85_866 nanoseconds.
Weight::from_ref_time(87_171_000 as u64)
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
fn retract_bid(l: u32, ) -> Weight {
// Minimum execution time: 44_605 nanoseconds.
Weight::from_ref_time(46_850_108 as u64)
// Standard Error: 135
.saturating_add(Weight::from_ref_time(34_178 as u64).saturating_mul(l as u64))
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
// Storage: Nis Active (r:1 w:1)
// Storage: Nis ActiveTotal (r:1 w:1)
fn thaw() -> Weight {
// Minimum execution time: 55_143 nanoseconds.
Weight::from_ref_time(55_845_000 as u64)
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
// Storage: Nis Active (r:1 w:1)
// Storage: Nis ActiveTotal (r:1 w:1)
fn fund_deficit() -> Weight {
Weight::from_ref_time(47_753_000 as u64)
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
// Storage: Nis ActiveTotal (r:1 w:0)
fn process_queues() -> Weight {
Weight::from_ref_time(1_663_000 as u64)
.saturating_add(RocksDbWeight::get().reads(1 as u64))
}
// Storage: Nis ActiveTotal (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis Active (r:0 w:1)
fn process_queue() -> Weight {
Weight::from_ref_time(40_797_000 as u64)
// Standard Error: 1_000
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
// Storage: Nis ActiveTotal (r:1 w:1)
// Storage: Nis QueueTotals (r:1 w:1)
// Storage: Nis Queues (r:1 w:1)
// Storage: Nis Active (r:0 w:1)
fn process_bid() -> Weight {
Weight::from_ref_time(14_944_000 as u64)
// Standard Error: 6_000
.saturating_add(RocksDbWeight::get().reads(2 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
}