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
+1 -1
View File
@@ -27,7 +27,7 @@ pub use tokens::{
},
fungible, fungibles,
imbalance::{Imbalance, OnUnbalanced, SignedImbalance},
BalanceStatus, ExistenceRequirement, Locker, WithdrawReasons,
nonfungible, nonfungibles, BalanceStatus, ExistenceRequirement, Locker, WithdrawReasons,
};
mod members;
+113 -5
View File
@@ -21,7 +21,7 @@ use crate::dispatch::Parameter;
use codec::{CompactLen, Decode, DecodeLimit, Encode, EncodeLike, Input, MaxEncodedLen};
use impl_trait_for_tuples::impl_for_tuples;
use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter};
use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, Saturating};
use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, One, Saturating};
use sp_core::bounded::bounded_vec::TruncateFrom;
#[doc(hidden)]
pub use sp_runtime::traits::{
@@ -348,17 +348,25 @@ impl<T> DefensiveOption<T> for Option<T> {
/// A variant of [`Defensive`] with the same rationale, for the arithmetic operations where in
/// case an infallible operation fails, it saturates.
pub trait DefensiveSaturating {
/// Add `self` and `other` defensively.
/// Return `self` plus `other` defensively.
fn defensive_saturating_add(self, other: Self) -> Self;
/// Subtract `other` from `self` defensively.
/// Return `self` minus `other` defensively.
fn defensive_saturating_sub(self, other: Self) -> Self;
/// Multiply `self` and `other` defensively.
/// Return the product of `self` and `other` defensively.
fn defensive_saturating_mul(self, other: Self) -> Self;
/// Increase `self` by `other` defensively.
fn defensive_saturating_accrue(&mut self, other: Self);
/// Reduce `self` by `other` defensively.
fn defensive_saturating_reduce(&mut self, other: Self);
/// Increment `self` by one defensively.
fn defensive_saturating_inc(&mut self);
/// Decrement `self` by one defensively.
fn defensive_saturating_dec(&mut self);
}
// NOTE: A bit unfortunate, since T has to be bound by all the traits needed. Could make it
// `DefensiveSaturating<T>` to mitigate.
impl<T: Saturating + CheckedAdd + CheckedMul + CheckedSub> DefensiveSaturating for T {
impl<T: Saturating + CheckedAdd + CheckedMul + CheckedSub + One> DefensiveSaturating for T {
fn defensive_saturating_add(self, other: Self) -> Self {
self.checked_add(&other).defensive_unwrap_or_else(|| self.saturating_add(other))
}
@@ -368,6 +376,20 @@ impl<T: Saturating + CheckedAdd + CheckedMul + CheckedSub> DefensiveSaturating f
fn defensive_saturating_mul(self, other: Self) -> Self {
self.checked_mul(&other).defensive_unwrap_or_else(|| self.saturating_mul(other))
}
fn defensive_saturating_accrue(&mut self, other: Self) {
// Use `replace` here since `take` would require `T: Default`.
*self = sp_std::mem::replace(self, One::one()).defensive_saturating_add(other);
}
fn defensive_saturating_reduce(&mut self, other: Self) {
// Use `replace` here since `take` would require `T: Default`.
*self = sp_std::mem::replace(self, One::one()).defensive_saturating_sub(other);
}
fn defensive_saturating_inc(&mut self) {
self.defensive_saturating_accrue(One::one());
}
fn defensive_saturating_dec(&mut self) {
self.defensive_saturating_reduce(One::one());
}
}
/// Construct an object by defensively truncating an input if the `TryFrom` conversion fails.
@@ -1119,6 +1141,92 @@ mod test {
use sp_core::bounded::{BoundedSlice, BoundedVec};
use sp_std::marker::PhantomData;
#[test]
#[cfg(not(debug_assertions))]
fn defensive_saturating_accrue_works() {
let mut v = 1_u32;
v.defensive_saturating_accrue(2);
assert_eq!(v, 3);
v.defensive_saturating_accrue(u32::MAX);
assert_eq!(v, u32::MAX);
v.defensive_saturating_accrue(1);
assert_eq!(v, u32::MAX);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "Defensive")]
fn defensive_saturating_accrue_panics() {
let mut v = u32::MAX;
v.defensive_saturating_accrue(1); // defensive failure
}
#[test]
#[cfg(not(debug_assertions))]
fn defensive_saturating_reduce_works() {
let mut v = u32::MAX;
v.defensive_saturating_reduce(3);
assert_eq!(v, u32::MAX - 3);
v.defensive_saturating_reduce(u32::MAX);
assert_eq!(v, 0);
v.defensive_saturating_reduce(1);
assert_eq!(v, 0);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "Defensive")]
fn defensive_saturating_reduce_panics() {
let mut v = 0_u32;
v.defensive_saturating_reduce(1); // defensive failure
}
#[test]
#[cfg(not(debug_assertions))]
fn defensive_saturating_inc_works() {
let mut v = 0_u32;
for i in 1..10 {
v.defensive_saturating_inc();
assert_eq!(v, i);
}
v += u32::MAX - 10;
v.defensive_saturating_inc();
assert_eq!(v, u32::MAX);
v.defensive_saturating_inc();
assert_eq!(v, u32::MAX);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "Defensive")]
fn defensive_saturating_inc_panics() {
let mut v = u32::MAX;
v.defensive_saturating_inc(); // defensive failure
}
#[test]
#[cfg(not(debug_assertions))]
fn defensive_saturating_dec_works() {
let mut v = u32::MAX;
for i in 1..10 {
v.defensive_saturating_dec();
assert_eq!(v, u32::MAX - i);
}
v -= u32::MAX - 10;
v.defensive_saturating_dec();
assert_eq!(v, 0);
v.defensive_saturating_dec();
assert_eq!(v, 0);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "Defensive")]
fn defensive_saturating_dec_panics() {
let mut v = 0_u32;
v.defensive_saturating_dec(); // defensive failure
}
#[test]
#[cfg(not(debug_assertions))]
fn defensive_truncating_from_vec_defensive_works() {
@@ -17,8 +17,14 @@
//! The reservable currency trait.
use scale_info::TypeInfo;
use sp_core::Get;
use super::{super::misc::BalanceStatus, Currency};
use crate::dispatch::{DispatchError, DispatchResult};
use crate::{
dispatch::{DispatchError, DispatchResult},
traits::{ExistenceRequirement, SignedImbalance, WithdrawReasons},
};
/// A currency where funds can be reserved from the user.
pub trait ReservableCurrency<AccountId>: Currency<AccountId> {
@@ -111,7 +117,7 @@ impl<AccountId> ReservableCurrency<AccountId> for () {
pub trait NamedReservableCurrency<AccountId>: ReservableCurrency<AccountId> {
/// An identifier for a reserve. Used for disambiguating different reserves so that
/// they can be individually replaced or removed.
type ReserveIdentifier;
type ReserveIdentifier: codec::Encode + TypeInfo + 'static;
/// Deducts up to `value` from reserved balance of `who`. This function cannot fail.
///
@@ -236,3 +242,144 @@ pub trait NamedReservableCurrency<AccountId>: ReservableCurrency<AccountId> {
Self::repatriate_reserved_named(id, slashed, beneficiary, value, status).map(|_| ())
}
}
/// Adapter to allow a `NamedReservableCurrency` to be passed as regular `ReservableCurrency`
/// together with an `Id`.
///
/// All "anonymous" operations are then implemented as their named counterparts with the given `Id`.
pub struct WithName<NamedReservable, Id, AccountId>(
sp_std::marker::PhantomData<(NamedReservable, Id, AccountId)>,
);
impl<
NamedReservable: NamedReservableCurrency<AccountId>,
Id: Get<NamedReservable::ReserveIdentifier>,
AccountId,
> Currency<AccountId> for WithName<NamedReservable, Id, AccountId>
{
type Balance = <NamedReservable as Currency<AccountId>>::Balance;
type PositiveImbalance = <NamedReservable as Currency<AccountId>>::PositiveImbalance;
type NegativeImbalance = <NamedReservable as Currency<AccountId>>::NegativeImbalance;
fn total_balance(who: &AccountId) -> Self::Balance {
NamedReservable::total_balance(who)
}
fn can_slash(who: &AccountId, value: Self::Balance) -> bool {
NamedReservable::can_slash(who, value)
}
fn total_issuance() -> Self::Balance {
NamedReservable::total_issuance()
}
fn minimum_balance() -> Self::Balance {
NamedReservable::minimum_balance()
}
fn burn(amount: Self::Balance) -> Self::PositiveImbalance {
NamedReservable::burn(amount)
}
fn issue(amount: Self::Balance) -> Self::NegativeImbalance {
NamedReservable::issue(amount)
}
fn pair(amount: Self::Balance) -> (Self::PositiveImbalance, Self::NegativeImbalance) {
NamedReservable::pair(amount)
}
fn free_balance(who: &AccountId) -> Self::Balance {
NamedReservable::free_balance(who)
}
fn ensure_can_withdraw(
who: &AccountId,
amount: Self::Balance,
reasons: WithdrawReasons,
new_balance: Self::Balance,
) -> DispatchResult {
NamedReservable::ensure_can_withdraw(who, amount, reasons, new_balance)
}
fn transfer(
source: &AccountId,
dest: &AccountId,
value: Self::Balance,
existence_requirement: ExistenceRequirement,
) -> DispatchResult {
NamedReservable::transfer(source, dest, value, existence_requirement)
}
fn slash(who: &AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) {
NamedReservable::slash(who, value)
}
fn deposit_into_existing(
who: &AccountId,
value: Self::Balance,
) -> Result<Self::PositiveImbalance, DispatchError> {
NamedReservable::deposit_into_existing(who, value)
}
fn resolve_into_existing(
who: &AccountId,
value: Self::NegativeImbalance,
) -> Result<(), Self::NegativeImbalance> {
NamedReservable::resolve_into_existing(who, value)
}
fn deposit_creating(who: &AccountId, value: Self::Balance) -> Self::PositiveImbalance {
NamedReservable::deposit_creating(who, value)
}
fn resolve_creating(who: &AccountId, value: Self::NegativeImbalance) {
NamedReservable::resolve_creating(who, value)
}
fn withdraw(
who: &AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> Result<Self::NegativeImbalance, DispatchError> {
NamedReservable::withdraw(who, value, reasons, liveness)
}
fn settle(
who: &AccountId,
value: Self::PositiveImbalance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> Result<(), Self::PositiveImbalance> {
NamedReservable::settle(who, value, reasons, liveness)
}
fn make_free_balance_be(
who: &AccountId,
balance: Self::Balance,
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance> {
NamedReservable::make_free_balance_be(who, balance)
}
}
impl<
NamedReservable: NamedReservableCurrency<AccountId>,
Id: Get<NamedReservable::ReserveIdentifier>,
AccountId,
> ReservableCurrency<AccountId> for WithName<NamedReservable, Id, AccountId>
{
fn can_reserve(who: &AccountId, value: Self::Balance) -> bool {
NamedReservable::can_reserve(who, value)
}
fn slash_reserved(
who: &AccountId,
value: Self::Balance,
) -> (Self::NegativeImbalance, Self::Balance) {
NamedReservable::slash_reserved_named(&Id::get(), who, value)
}
fn reserved_balance(who: &AccountId) -> Self::Balance {
NamedReservable::reserved_balance_named(&Id::get(), who)
}
fn reserve(who: &AccountId, value: Self::Balance) -> DispatchResult {
NamedReservable::reserve_named(&Id::get(), who, value)
}
fn unreserve(who: &AccountId, value: Self::Balance) -> Self::Balance {
NamedReservable::unreserve_named(&Id::get(), who, value)
}
fn repatriate_reserved(
slashed: &AccountId,
beneficiary: &AccountId,
value: Self::Balance,
status: BalanceStatus,
) -> Result<Self::Balance, DispatchError> {
NamedReservable::repatriate_reserved_named(&Id::get(), slashed, beneficiary, value, status)
}
}
@@ -83,7 +83,7 @@ pub trait Mutate<AccountId>: Inspect<AccountId> {
/// is returned and nothing is changed. If successful, the amount of tokens reduced is returned.
///
/// The default implementation just uses `withdraw` along with `reducible_balance` to ensure
/// that is doesn't fail.
/// that it doesn't fail.
fn slash(who: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
Self::burn_from(who, Self::reducible_balance(who, false).min(amount))
}