mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 04:07:57 +00:00
49b6dfd2e5
* Cargo.lock after merge * Restore scale-info feature * Fully qualify TypeInfo derive * Skip PendingSwap T * Add missing skip_type_params attr * metadata docs features * Reduce pallet event attribute to struct * Cargo.lock * Update frame/balances/src/tests_composite.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Line widths check * Cargo.lock * Add scale-info/std * Update frame/system/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Use `skip_type_params` to remove `TypeInfo` requirements on checks * Revert "Remove unused Call metadata stuff" This reverts commit 41311f85 * Skip BalanceSwapAction type parameter * Remove unused event metadata macro * Update frame-metadata * Update primitives/npos-elections/compact/src/codec.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Manual TypeInfo for Header * Remove TypeInfo requirement for consts in BoundedVec etc. * Another TypeInfo bound removed * review: fix indentation * TypeInfo impls for Identity types * Add some todos to add custom TypeInfo impls * Update frame/support/procedural/src/pallet/expand/pallet_struct.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Add some todos to add custom TypeInfo impls * Add a test for manual Data TypeInfo impl * Add custom TypeInfo impl for Vote * Era custom TypeInfo crimes * Revert finality-grandpa version to 0.14.z * review: renamed module to pallet_constants_metadata * New line at end of file * Add missing scale-info/std * Update frame/support/src/storage/types/mod.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Remove StorageEntryType::Map unused flag * Add missing scale-info dependency after merge * SignedExtension::AdditionalSigned metadata * Update frame-metadata, use abbreviated docs and args fields * Update frame/example/Cargo.toml Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Add scale_info/std and remove unused scale-info dependency * Remove scale-info dependency * Remove treasury pallet::metadata * Remove redundant Event test * Add back scale-info as dev dependency * fix error metadata when no error defined in decl_module * Add Module3 to tests * Fix metadata test * Add docs feature to frame-support test * WIP fixing pallet metadata test * Remove redundant FunctionMetadata, FunctionArgumentMetadata as per https://github.com/paritytech/frame-metadata/pull/20 * Use main branch of frame-metadata * Use patch of scale-info for latest changes * Use latest patched scale-info * Manual TypeInfo for DigestItem * Manual TypeInfo for DigestItem * Update scale-info * Skip __Ignore variants for Error, depends on https://github.com/paritytech/scale-info/pull/117 * Named fields for FRAME v2 pallet Call variants * Named fields for FRAME v1 pallet Call variants * Add missing scale-info dependency * WIP expand benchmark call variant * fix benchmark with new function create a new function for each variant of a pallet call. This function is called by benchmarking macro in order not to break call creation with unnamed argument * fix tests * more fix * Fix staking tests * Fix offchain workers calls * Cherry pick rustfmt.toml from master * cargo +nightly-2021-06-22 fmt --all * Update to new call variant structs * More call variant struct updates * Remove unused import * More call variant structs * More call variant structs * Even more call variant structs * Mooar variant structs * Evermore variant structs * Call variant structs ad infinitum * Fmt * More call variants * Last call variant * Call variants all done? * Fix SS58Prefix type * Potential workaround for BitFlags<IdentityFields> TypeInfo * Enable docs capturing for Call, Event, and Error types * Fix IdentityFields TypeInfo * Remove metadata-docs feature * Add capture_docs = true for legacy Call, Event and Error types * Fmt * Fix metadata test type * Update benchmarks with call struct variants * Fmt * More test fixes * Fmt * Fix benches * Use latest capture_docs attr * Latest scale_info * Fmt * review: change &Vec to &[] * Remove pallet metadata attr * review: remove commented out test code * review: skip_type_params trailing comma suggestion * Update to scale-info 0.10.0 * Update construct_runtime ui tests, different because of metadata TypeInfo impls * Add some TypeInfo derives for UI tests * Update storage ensure span ui stderrs * Update call argument bound ui tests Possibly changed because change from tuple to struct variants? * Add scale-info dev dependency * Update to latest finality-grandpa release * review: missing newline * review: missing scale-info/std * review: remove duplicate scale-info/std * review: remove fully qualified TypeInfo * review: add missing scale-info/std * review: remove unnecessary imports. * Fmt * Use crates.io RC version of frame-metadata * Remove scale-info/std because it is a dev dependency * Add missing scale_info dev-dependency for test * Delete empty metadata folder * Fix sp_std import * review: improve manual UncheckedExtrinsic TypeInfo impl * review: use full scale-info for dev-dependency * Remove DefaultByteGetter impl * review: derive TypeInfo for generic header * Fmt * Update primitives/runtime/src/generic/unchecked_extrinsic.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Update primitives/runtime/src/generic/unchecked_extrinsic.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Update bin/node/executor/Cargo.toml Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update frame/identity/src/types.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update frame/support/src/dispatch.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Remove redundant derive * Simplify scale-info dependency * Strip underscore prefix from call variant struct names * Another underscore field * More underscore fields * Another underscore field * Update to frame-metadata 14.0.0-rc.2 with combined StorageEntryType::Map * Fmt * Revert weights formatting * Fix up some tests * Fix up some tests for StorageEntryTypeMetadata * scale-info dev dependency * Fix test error * Add missing TypeInfo derives * Add back missing scale-info dependency * Add back missing scale-info dependency * Fix npos compact impls * Cargo.lock * Fmt * Fix errors * Fmt * Fix renamed raw_solution field * Fix error * Fmt * Fix some benchmarks * Fmt * Stray R * Fix * Add missing TypeInfos * ui test fix * Fix line widths * Revert "ui test fix" This reverts commit 2d15ec058a216e3f92d713f1174603a2bb1eac65. * Upgrade to scale-info 0.11.0 * Revert "Upgrade to scale-info 0.11.0" This reverts commit 047bb179085a0059c36cd20ab405f55cf0867e28. * Add Runtime type * Update to scale-info 0.12 * Update to scale-info 1.0 * Update frame-metadata to version 14.0.0 * Patch finality-grandpa until release available * Fix metadata tests * Fix metadata tests * Fmt * Remove patched finality-grandpa * Fix tests, use scale_info imports * Fix pallet tests * Add BlockNumber TypeInfo bound * ui test fix * Cargo.lock * Remove pallet metadata * Cargo.lock * Add missing scale-info dependency * Remove pallet event metadata * Fix error * Fix collective errors * Semicolol * Fmt * Remove another metadata attribute * Add new variant to custom digest TypeInfo * Fmt * Cargo.lock from master * Remove comma lol * Fix example call error * Fix example call error properly Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
632 lines
23 KiB
Rust
632 lines
23 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! # Gilt 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 `MinFreeze` to avoid dust.
|
|
//!
|
|
//! Until your bid is turned into an issued gilt you can retract it instantly and the funds are
|
|
//! unreserved.
|
|
//!
|
|
//! There's a target proportion of effective total issuance (i.e. accounting for existing gilts)
|
|
//! which the we attempt to have frozen at any one time. It will likely be gradually increased over
|
|
//! time by governance.
|
|
//!
|
|
//! As the total funds frozen under gilts drops below `FrozenFraction` of the total effective
|
|
//! issuance, then bids are taken from queues, with the queue of the greatest period taking
|
|
//! priority. If the item in the queue's locked amount is greater than the amount left to be
|
|
//! frozen, then it is split up into multiple bids and becomes partially frozen under gilt.
|
|
//!
|
|
//! Once an account's balance is frozen, it remains frozen until the owner thaws the balance of the
|
|
//! account. This may happen no earlier than queue's period after the point at which the gilt is
|
|
//! issued.
|
|
//!
|
|
//! ## Suggested Values
|
|
//!
|
|
//! - `QueueCount`: 300
|
|
//! - `Period`: 432,000
|
|
//! - `MaxQueueLen`: 1000
|
|
//! - `MinFreeze`: Around CHF 100 in value.
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
pub use pallet::*;
|
|
|
|
mod benchmarking;
|
|
#[cfg(test)]
|
|
mod mock;
|
|
#[cfg(test)]
|
|
mod tests;
|
|
pub mod weights;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
pub use crate::weights::WeightInfo;
|
|
use frame_support::{
|
|
pallet_prelude::*,
|
|
traits::{Currency, OnUnbalanced, ReservableCurrency},
|
|
};
|
|
use frame_system::pallet_prelude::*;
|
|
use scale_info::TypeInfo;
|
|
use sp_arithmetic::{PerThing, Perquintill};
|
|
use sp_runtime::traits::{Saturating, Zero};
|
|
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 NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
|
<T as frame_system::Config>::AccountId,
|
|
>>::NegativeImbalance;
|
|
|
|
#[pallet::config]
|
|
pub trait Config: frame_system::Config {
|
|
/// Overarching event type.
|
|
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
|
|
|
|
/// 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;
|
|
|
|
/// Origin required for setting the target proportion to be under gilt.
|
|
type AdminOrigin: EnsureOrigin<Self::Origin>;
|
|
|
|
/// Unbalanced handler to account for funds created (in case of a higher total issuance over
|
|
/// freezing period).
|
|
type Deficit: OnUnbalanced<PositiveImbalanceOf<Self>>;
|
|
|
|
/// Unbalanced handler to account for funds destroyed (in case of a lower total issuance
|
|
/// over freezing period).
|
|
type Surplus: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
|
|
|
/// The issuance to ignore. This is subtracted from the `Currency`'s `total_issuance` to get
|
|
/// the issuance by which we inflate or deflate the gilt.
|
|
#[pallet::constant]
|
|
type IgnoredIssuance: Get<BalanceOf<Self>>;
|
|
|
|
/// 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.
|
|
#[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 Period: Get<Self::BlockNumber>;
|
|
|
|
/// The minimum amount of funds that may be offered to freeze for a gilt. Note that this
|
|
/// does not actually limit the amount which may be frozen in a gilt since gilts may be
|
|
/// split up in order to satisfy the desired amount of funds under gilts.
|
|
///
|
|
/// It should be at least big enough to ensure that there is no possible storage spam attack
|
|
/// or queue-filling attack.
|
|
#[pallet::constant]
|
|
type MinFreeze: Get<BalanceOf<Self>>;
|
|
|
|
/// The number of blocks between consecutive attempts to issue more gilts in an effort to
|
|
/// get to the target amount to be frozen.
|
|
///
|
|
/// 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 be turned into issued gilts each block. A larger
|
|
/// value here means less of the block available for transactions should there be a glut of
|
|
/// bids to make into gilts to reach the target.
|
|
#[pallet::constant]
|
|
type MaxIntakeBids: Get<u32>;
|
|
|
|
/// Information on runtime weights.
|
|
type WeightInfo: WeightInfo;
|
|
}
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::generate_store(pub(super) trait Store)]
|
|
pub struct Pallet<T>(_);
|
|
|
|
/// A single bid on a gilt, an item of a *queue* in `Queues`.
|
|
#[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)]
|
|
pub struct GiltBid<Balance, AccountId> {
|
|
/// The amount bid.
|
|
pub amount: Balance,
|
|
/// The owner of the bid.
|
|
pub who: AccountId,
|
|
}
|
|
|
|
/// Information representing an active gilt.
|
|
#[derive(Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo)]
|
|
pub struct ActiveGilt<Balance, AccountId, BlockNumber> {
|
|
/// The proportion of the effective total issuance (i.e. accounting for any eventual gilt
|
|
/// expansion or contraction that may eventually be claimed).
|
|
pub proportion: Perquintill,
|
|
/// The amount reserved under this gilt.
|
|
pub amount: Balance,
|
|
/// The account to whom this gilt belongs.
|
|
pub who: AccountId,
|
|
/// The time after which this gilt can be redeemed for the proportional amount of balance.
|
|
pub expiry: BlockNumber,
|
|
}
|
|
|
|
/// An index for a gilt.
|
|
pub type ActiveIndex = u32;
|
|
|
|
/// Overall information package on the active gilts.
|
|
///
|
|
/// 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)]
|
|
pub struct ActiveGiltsTotal<Balance> {
|
|
/// The total amount of funds held in reserve for all active gilts.
|
|
pub frozen: Balance,
|
|
/// The proportion of funds that the `frozen` balance represents to total issuance.
|
|
pub proportion: Perquintill,
|
|
/// The total number of gilts issued so far.
|
|
pub index: ActiveIndex,
|
|
/// The target proportion of gilts within total issuance.
|
|
pub target: Perquintill,
|
|
}
|
|
|
|
/// 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> = StorageValue<_, Vec<(u32, BalanceOf<T>)>, ValueQuery>;
|
|
|
|
/// The queues of bids ready to become gilts. Indexed by duration (in `Period`s).
|
|
#[pallet::storage]
|
|
pub type Queues<T: Config> =
|
|
StorageMap<_, Blake2_128Concat, u32, Vec<GiltBid<BalanceOf<T>, T::AccountId>>, ValueQuery>;
|
|
|
|
/// Information relating to the gilts currently active.
|
|
#[pallet::storage]
|
|
pub type ActiveTotal<T> = StorageValue<_, ActiveGiltsTotal<BalanceOf<T>>, ValueQuery>;
|
|
|
|
/// The currently active gilts, indexed according to the order of creation.
|
|
#[pallet::storage]
|
|
pub type Active<T> = StorageMap<
|
|
_,
|
|
Blake2_128Concat,
|
|
ActiveIndex,
|
|
ActiveGilt<
|
|
BalanceOf<T>,
|
|
<T as frame_system::Config>::AccountId,
|
|
<T as frame_system::Config>::BlockNumber,
|
|
>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::genesis_config]
|
|
#[derive(Default)]
|
|
pub struct GenesisConfig;
|
|
|
|
#[pallet::genesis_build]
|
|
impl<T: Config> GenesisBuild<T> for GenesisConfig {
|
|
fn build(&self) {
|
|
QueueTotals::<T>::put(vec![(0, BalanceOf::<T>::zero()); T::QueueCount::get() as usize]);
|
|
}
|
|
}
|
|
|
|
#[pallet::event]
|
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
pub enum Event<T: Config> {
|
|
/// A bid was successfully placed.
|
|
/// \[ who, amount, duration \]
|
|
BidPlaced(T::AccountId, BalanceOf<T>, u32),
|
|
/// A bid was successfully removed (before being accepted as a gilt).
|
|
/// \[ who, amount, duration \]
|
|
BidRetracted(T::AccountId, BalanceOf<T>, u32),
|
|
/// A bid was accepted as a gilt. The balance may not be released until expiry.
|
|
/// \[ index, expiry, who, amount \]
|
|
GiltIssued(ActiveIndex, T::BlockNumber, T::AccountId, BalanceOf<T>),
|
|
/// An expired gilt has been thawed.
|
|
/// \[ index, who, original_amount, additional_amount \]
|
|
GiltThawed(ActiveIndex, T::AccountId, BalanceOf<T>, BalanceOf<T>),
|
|
}
|
|
|
|
#[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,
|
|
/// Gilt index is unknown.
|
|
Unknown,
|
|
/// Not the owner of the gilt.
|
|
NotOwner,
|
|
/// Gilt not yet at expiry date.
|
|
NotExpired,
|
|
/// The given bid for retraction is not found.
|
|
NotFound,
|
|
}
|
|
|
|
#[pallet::hooks]
|
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
|
fn on_initialize(n: T::BlockNumber) -> Weight {
|
|
if (n % T::IntakePeriod::get()).is_zero() {
|
|
Self::pursue_target(T::MaxIntakeBids::get())
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pallet::call]
|
|
impl<T: Config> Pallet<T> {
|
|
/// Place a bid for a gilt to be issued.
|
|
///
|
|
/// 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. If the bid is
|
|
/// successfully elevated into an issued gilt, then these funds will continue to be
|
|
/// reserved until the gilt expires. Must be at least `MinFreeze`.
|
|
/// - `duration`: The number of periods for which the funds will be locked if the gilt is
|
|
/// issued. It will expire only after this period has elapsed after the point of issuance.
|
|
/// 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,
|
|
) -> DispatchResultWithPostInfo {
|
|
let who = ensure_signed(origin)?;
|
|
|
|
ensure!(amount >= T::MinFreeze::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 = GiltBid { amount, who: who.clone() };
|
|
let net = if queue_full {
|
|
sp_std::mem::swap(&mut q[0], &mut bid);
|
|
T::Currency::unreserve(&bid.who, bid.amount);
|
|
(0, amount - bid.amount)
|
|
} else {
|
|
q.insert(0, bid);
|
|
(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.resize(queue_count, (0, Zero::zero()));
|
|
qs[queue_index].0 += net.0;
|
|
qs[queue_index].1 = qs[queue_index].1.saturating_add(net.1);
|
|
});
|
|
Self::deposit_event(Event::BidPlaced(who.clone(), amount, duration));
|
|
|
|
Ok(().into())
|
|
}
|
|
|
|
/// 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::place_bid(T::MaxQueueLen::get()))]
|
|
pub fn retract_bid(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] amount: BalanceOf<T>,
|
|
duration: u32,
|
|
) -> DispatchResultWithPostInfo {
|
|
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 = GiltBid { 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.resize(queue_count, (0, Zero::zero()));
|
|
qs[queue_index].0 = new_len;
|
|
qs[queue_index].1 = qs[queue_index].1.saturating_sub(bid.amount);
|
|
});
|
|
|
|
T::Currency::unreserve(&bid.who, bid.amount);
|
|
Self::deposit_event(Event::BidRetracted(bid.who, bid.amount, duration));
|
|
|
|
Ok(().into())
|
|
}
|
|
|
|
/// Set target proportion of gilt-funds.
|
|
///
|
|
/// Origin must be `AdminOrigin`.
|
|
///
|
|
/// - `target`: The target proportion of effective issued funds that should be under gilts
|
|
/// at any one time.
|
|
#[pallet::weight(T::WeightInfo::set_target())]
|
|
pub fn set_target(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] target: Perquintill,
|
|
) -> DispatchResultWithPostInfo {
|
|
T::AdminOrigin::ensure_origin(origin)?;
|
|
ActiveTotal::<T>::mutate(|totals| totals.target = target);
|
|
Ok(().into())
|
|
}
|
|
|
|
/// Remove an active but expired gilt. Reserved funds under gilt are freed and balance is
|
|
/// adjusted to ensure that the funds grow or shrink to maintain the equivalent proportion
|
|
/// of effective total issued funds.
|
|
///
|
|
/// Origin must be Signed and the account must be the owner of the gilt of the given index.
|
|
///
|
|
/// - `index`: The index of the gilt to be thawed.
|
|
#[pallet::weight(T::WeightInfo::thaw())]
|
|
pub fn thaw(
|
|
origin: OriginFor<T>,
|
|
#[pallet::compact] index: ActiveIndex,
|
|
) -> DispatchResultWithPostInfo {
|
|
let who = ensure_signed(origin)?;
|
|
|
|
// Look for `index`
|
|
let gilt = Active::<T>::get(index).ok_or(Error::<T>::Unknown)?;
|
|
// If found, check the owner is `who`.
|
|
ensure!(gilt.who == who, Error::<T>::NotOwner);
|
|
let now = frame_system::Pallet::<T>::block_number();
|
|
ensure!(now >= gilt.expiry, Error::<T>::NotExpired);
|
|
// Remove it
|
|
Active::<T>::remove(index);
|
|
|
|
// Multiply the proportion it is by the total issued.
|
|
let total_issuance =
|
|
T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get());
|
|
ActiveTotal::<T>::mutate(|totals| {
|
|
let nongilt_issuance = total_issuance.saturating_sub(totals.frozen);
|
|
let effective_issuance =
|
|
totals.proportion.left_from_one().saturating_reciprocal_mul(nongilt_issuance);
|
|
let gilt_value = gilt.proportion * effective_issuance;
|
|
|
|
totals.frozen = totals.frozen.saturating_sub(gilt.amount);
|
|
totals.proportion = totals.proportion.saturating_sub(gilt.proportion);
|
|
|
|
// Remove or mint the additional to the amount using `Deficit`/`Surplus`.
|
|
if gilt_value > gilt.amount {
|
|
// Unreserve full amount.
|
|
T::Currency::unreserve(&gilt.who, gilt.amount);
|
|
let amount = gilt_value - gilt.amount;
|
|
let deficit = T::Currency::deposit_creating(&gilt.who, amount);
|
|
T::Deficit::on_unbalanced(deficit);
|
|
} else {
|
|
if gilt_value < gilt.amount {
|
|
// We take anything reserved beyond the gilt's final value.
|
|
let rest = gilt.amount - gilt_value;
|
|
// `slash` might seem a little aggressive, but it's the only way to do it
|
|
// in case it's locked into the staking system.
|
|
let surplus = T::Currency::slash_reserved(&gilt.who, rest).0;
|
|
T::Surplus::on_unbalanced(surplus);
|
|
}
|
|
// Unreserve only its new value (less than the amount reserved). Everything
|
|
// should add up, but (defensive) in case it doesn't, unreserve takes lower
|
|
// priority over the funds.
|
|
let err_amt = T::Currency::unreserve(&gilt.who, gilt_value);
|
|
debug_assert!(err_amt.is_zero());
|
|
}
|
|
|
|
let e = Event::GiltThawed(index, gilt.who, gilt.amount, gilt_value);
|
|
Self::deposit_event(e);
|
|
});
|
|
|
|
Ok(().into())
|
|
}
|
|
}
|
|
|
|
/// Issuance information returned by `issuance()`.
|
|
pub struct IssuanceInfo<Balance> {
|
|
/// The balance held in reserve over all active gilts.
|
|
pub reserved: Balance,
|
|
/// The issuance not held in reserve for active gilts. Together with `reserved` this sums
|
|
/// to `Currency::total_issuance`.
|
|
pub non_gilt: Balance,
|
|
/// The balance that `reserved` is effectively worth, at present. This is not issued funds
|
|
/// and could be less than `reserved` (though in most cases should be greater).
|
|
pub effective: Balance,
|
|
}
|
|
|
|
impl<T: Config> Pallet<T> {
|
|
/// Get the target amount of Gilts that we're aiming for.
|
|
pub fn target() -> Perquintill {
|
|
ActiveTotal::<T>::get().target
|
|
}
|
|
|
|
/// Returns information on the issuance of gilts.
|
|
pub fn issuance() -> IssuanceInfo<BalanceOf<T>> {
|
|
let totals = ActiveTotal::<T>::get();
|
|
|
|
let total_issuance = T::Currency::total_issuance();
|
|
let non_gilt = total_issuance.saturating_sub(totals.frozen);
|
|
let effective = totals.proportion.left_from_one().saturating_reciprocal_mul(non_gilt);
|
|
|
|
IssuanceInfo { reserved: totals.frozen, non_gilt, effective }
|
|
}
|
|
|
|
/// Attempt to enlarge our gilt-set from bids in order to satisfy our desired target amount
|
|
/// of funds frozen into gilts.
|
|
pub fn pursue_target(max_bids: u32) -> Weight {
|
|
let totals = ActiveTotal::<T>::get();
|
|
if totals.proportion < totals.target {
|
|
let missing = totals.target.saturating_sub(totals.proportion);
|
|
|
|
let total_issuance =
|
|
T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get());
|
|
let nongilt_issuance = total_issuance.saturating_sub(totals.frozen);
|
|
let effective_issuance =
|
|
totals.proportion.left_from_one().saturating_reciprocal_mul(nongilt_issuance);
|
|
let intake = missing * effective_issuance;
|
|
|
|
let (bids_taken, queues_hit) = Self::enlarge(intake, max_bids);
|
|
let first_from_each_queue = T::WeightInfo::pursue_target_per_queue(queues_hit);
|
|
let rest_from_each_queue = T::WeightInfo::pursue_target_per_item(bids_taken)
|
|
.saturating_sub(T::WeightInfo::pursue_target_per_item(queues_hit));
|
|
first_from_each_queue + rest_from_each_queue
|
|
} else {
|
|
T::WeightInfo::pursue_target_noop()
|
|
}
|
|
}
|
|
|
|
/// Freeze additional funds from queue of bids up to `amount`. Use at most `max_bids`
|
|
/// from the queue.
|
|
///
|
|
/// Return the number of bids taken and the number of distinct queues taken from.
|
|
pub fn enlarge(amount: BalanceOf<T>, max_bids: u32) -> (u32, u32) {
|
|
let total_issuance =
|
|
T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get());
|
|
let mut remaining = amount;
|
|
let mut bids_taken = 0;
|
|
let mut queues_hit = 0;
|
|
let now = frame_system::Pallet::<T>::block_number();
|
|
|
|
ActiveTotal::<T>::mutate(|totals| {
|
|
QueueTotals::<T>::mutate(|qs| {
|
|
for duration in (1..=T::QueueCount::get()).rev() {
|
|
if qs[duration as usize - 1].0 == 0 {
|
|
continue
|
|
}
|
|
let queue_index = duration as usize - 1;
|
|
let expiry =
|
|
now.saturating_add(T::Period::get().saturating_mul(duration.into()));
|
|
Queues::<T>::mutate(duration, |q| {
|
|
while let Some(mut bid) = q.pop() {
|
|
if remaining < bid.amount {
|
|
let overflow = bid.amount - remaining;
|
|
bid.amount = remaining;
|
|
q.push(GiltBid { amount: overflow, who: bid.who.clone() });
|
|
}
|
|
let amount = bid.amount;
|
|
// Can never overflow due to block above.
|
|
remaining -= amount;
|
|
// Should never underflow since it should track the total of the
|
|
// bids exactly, but we'll be defensive.
|
|
qs[queue_index].1 = qs[queue_index].1.saturating_sub(bid.amount);
|
|
|
|
// Now to activate the bid...
|
|
let nongilt_issuance = total_issuance.saturating_sub(totals.frozen);
|
|
let effective_issuance = totals
|
|
.proportion
|
|
.left_from_one()
|
|
.saturating_reciprocal_mul(nongilt_issuance);
|
|
let n = amount;
|
|
let d = effective_issuance;
|
|
let proportion = Perquintill::from_rational(n, d);
|
|
let who = bid.who;
|
|
let index = totals.index;
|
|
totals.frozen += bid.amount;
|
|
totals.proportion = totals.proportion.saturating_add(proportion);
|
|
totals.index += 1;
|
|
let e = Event::GiltIssued(index, expiry, who.clone(), amount);
|
|
Self::deposit_event(e);
|
|
let gilt = ActiveGilt { amount, proportion, who, expiry };
|
|
Active::<T>::insert(index, gilt);
|
|
|
|
bids_taken += 1;
|
|
|
|
if remaining.is_zero() || bids_taken == max_bids {
|
|
break
|
|
}
|
|
}
|
|
queues_hit += 1;
|
|
qs[queue_index].0 = q.len() as u32;
|
|
});
|
|
if remaining.is_zero() || bids_taken == max_bids {
|
|
break
|
|
}
|
|
}
|
|
});
|
|
});
|
|
(bids_taken, queues_hit)
|
|
}
|
|
}
|
|
}
|