mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 20:27:58 +00:00
Several tweaks needed for Governance 2.0 (#11124)
* Add stepped curve for referenda * Treasury SpendOrigin * Add tests * Better Origin Or-gating * Reciprocal curve * Tests for reciprical and rounding in PerThings * Tweaks and new quad curve * Const derivation of reciprocal curve parameters * Remove some unneeded code * Actually useful linear curve * Fixes * Provisional curves * Rejig 'turnout' as 'support' * Use TypedGet * Fixes * Enable curve's ceil to be configured * Formatting * Fixes * Fixes * Fixes * Remove EnsureOneOf * Fixes * Fixes * Fixes * Formatting * Fixes * Update frame/support/src/traits/dispatch.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Grumbles * Formatting * Fixes * APIs of VoteTally should include class * Fixes * Fix overlay prefix removal result * Second part of the overlay prefix removal fix. * Formatting * Fixes * Add some tests and make clear rounding algo * Fixes * Formatting * Revert questionable fix * Introduce test for kill_prefix * Fixes * Formatting * Fixes * Fix possible overflow * Docs * Add benchmark test * Formatting * Update frame/referenda/src/types.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * Docs * Fixes * Use latest API in tests * Formatting * Whitespace * Use latest API in tests Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
This commit is contained in:
@@ -18,12 +18,12 @@
|
||||
//! Decimal Fixed Point implementations for Substrate runtime.
|
||||
|
||||
use crate::{
|
||||
helpers_128bit::multiply_by_rational,
|
||||
helpers_128bit::{multiply_by_rational, multiply_by_rational_with_rounding, sqrt},
|
||||
traits::{
|
||||
Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedSub, One,
|
||||
SaturatedConversion, Saturating, UniqueSaturatedInto, Zero,
|
||||
},
|
||||
PerThing,
|
||||
PerThing, Perbill, Rounding, SignedRounding,
|
||||
};
|
||||
use codec::{CompactAs, Decode, Encode};
|
||||
use sp_std::{
|
||||
@@ -406,20 +406,326 @@ macro_rules! implement_fixed {
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// const version of `FixedPointNumber::from_inner`.
|
||||
/// Create a new instance from the given `inner` value.
|
||||
///
|
||||
/// `const` version of `FixedPointNumber::from_inner`.
|
||||
pub const fn from_inner(inner: $inner_type) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
/// Return the instance's inner value.
|
||||
///
|
||||
/// `const` version of `FixedPointNumber::into_inner`.
|
||||
pub const fn into_inner(self) -> $inner_type {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Creates self from a `u32`.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn from_u32(n: u32) -> Self {
|
||||
Self::from_inner((n as $inner_type) * $div)
|
||||
}
|
||||
|
||||
/// Convert from a `float` value.
|
||||
#[cfg(any(feature = "std", test))]
|
||||
pub fn from_float(x: f64) -> Self {
|
||||
Self((x * (<Self as FixedPointNumber>::DIV as f64)) as $inner_type)
|
||||
}
|
||||
|
||||
/// Convert from a `Perbill` value.
|
||||
pub const fn from_perbill(n: Perbill) -> Self {
|
||||
Self::from_rational(n.deconstruct() as u128, 1_000_000_000)
|
||||
}
|
||||
|
||||
/// Convert into a `Perbill` value. Will saturate if above one or below zero.
|
||||
pub const fn into_perbill(self) -> Perbill {
|
||||
if self.0 <= 0 {
|
||||
Perbill::zero()
|
||||
} else if self.0 >= $div {
|
||||
Perbill::one()
|
||||
} else {
|
||||
match multiply_by_rational_with_rounding(
|
||||
self.0 as u128,
|
||||
1_000_000_000,
|
||||
Self::DIV as u128,
|
||||
Rounding::NearestPrefDown,
|
||||
) {
|
||||
Some(value) => {
|
||||
if value > (u32::max_value() as u128) {
|
||||
panic!(
|
||||
"prior logic ensures 0<self.0<DIV; \
|
||||
multiply ensures 0<self.0<1000000000; \
|
||||
qed"
|
||||
);
|
||||
}
|
||||
Perbill::from_parts(value as u32)
|
||||
},
|
||||
None => Perbill::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert into a `float` value.
|
||||
#[cfg(any(feature = "std", test))]
|
||||
pub fn to_float(self) -> f64 {
|
||||
self.0 as f64 / <Self as FixedPointNumber>::DIV as f64
|
||||
}
|
||||
|
||||
/// Attempt to convert into a `PerThing`. This will succeed iff `self` is at least zero
|
||||
/// and at most one. If it is out of bounds, it will result in an error returning the
|
||||
/// clamped value.
|
||||
pub fn try_into_perthing<P: PerThing>(self) -> Result<P, P> {
|
||||
if self < Self::zero() {
|
||||
Err(P::zero())
|
||||
} else if self > Self::one() {
|
||||
Err(P::one())
|
||||
} else {
|
||||
Ok(P::from_rational(self.0 as u128, $div))
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert into a `PerThing`. This will always succeed resulting in a
|
||||
/// clamped value if `self` is less than zero or greater than one.
|
||||
pub fn into_clamped_perthing<P: PerThing>(self) -> P {
|
||||
if self < Self::zero() {
|
||||
P::zero()
|
||||
} else if self > Self::one() {
|
||||
P::one()
|
||||
} else {
|
||||
P::from_rational(self.0 as u128, $div)
|
||||
}
|
||||
}
|
||||
|
||||
/// Negate the value.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn neg(self) -> Self {
|
||||
Self(0 - self.0)
|
||||
}
|
||||
|
||||
/// Take the square root of a positive value.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn sqrt(self) -> Self {
|
||||
match self.try_sqrt() {
|
||||
Some(v) => v,
|
||||
None => panic!("sqrt overflow or negative input"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the square root, rounding as desired. If it overflows or is negative, then
|
||||
/// `None` is returned.
|
||||
pub const fn try_sqrt(self) -> Option<Self> {
|
||||
if self.0 == 0 {
|
||||
return Some(Self(0))
|
||||
}
|
||||
if self.0 < 1 {
|
||||
return None
|
||||
}
|
||||
let v = self.0 as u128;
|
||||
|
||||
// Want x' = sqrt(x) where x = n/D and x' = n'/D (D is fixed)
|
||||
// Our prefered way is:
|
||||
// sqrt(n/D) = sqrt(nD / D^2) = sqrt(nD)/sqrt(D^2) = sqrt(nD)/D
|
||||
// ergo n' = sqrt(nD)
|
||||
// but this requires nD to fit into our type.
|
||||
// if nD doesn't fit then we can fall back on:
|
||||
// sqrt(nD) = sqrt(n)*sqrt(D)
|
||||
// computing them individually and taking the product at the end. we will lose some
|
||||
// precision though.
|
||||
let maybe_vd = u128::checked_mul(v, $div);
|
||||
let r = if let Some(vd) = maybe_vd { sqrt(vd) } else { sqrt(v) * sqrt($div) };
|
||||
Some(Self(r as $inner_type))
|
||||
}
|
||||
|
||||
/// Add a value and return the result.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn add(self, rhs: Self) -> Self {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
|
||||
/// Subtract a value and return the result.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn sub(self, rhs: Self) -> Self {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
|
||||
/// Multiply by a value and return the result.
|
||||
///
|
||||
/// Result will be rounded to the nearest representable value, rounding down if it is
|
||||
/// equidistant between two neighbours.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn mul(self, rhs: Self) -> Self {
|
||||
match $name::const_checked_mul(self, rhs) {
|
||||
Some(v) => v,
|
||||
None => panic!("attempt to multiply with overflow"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Divide by a value and return the result.
|
||||
///
|
||||
/// Result will be rounded to the nearest representable value, rounding down if it is
|
||||
/// equidistant between two neighbours.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn div(self, rhs: Self) -> Self {
|
||||
match $name::const_checked_div(self, rhs) {
|
||||
Some(v) => v,
|
||||
None => panic!("attempt to divide with overflow or NaN"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert into an `I129` format value.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
const fn into_i129(self) -> I129 {
|
||||
#[allow(unused_comparisons)]
|
||||
if self.0 < 0 {
|
||||
let value = match self.0.checked_neg() {
|
||||
Some(n) => n as u128,
|
||||
None => u128::saturating_add(<$inner_type>::max_value() as u128, 1),
|
||||
};
|
||||
I129 { value, negative: true }
|
||||
} else {
|
||||
I129 { value: self.0 as u128, negative: false }
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from an `I129` format value.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
const fn from_i129(n: I129) -> Option<Self> {
|
||||
let max_plus_one = u128::saturating_add(<$inner_type>::max_value() as u128, 1);
|
||||
#[allow(unused_comparisons)]
|
||||
let inner = if n.negative && <$inner_type>::min_value() < 0 && n.value == max_plus_one {
|
||||
<$inner_type>::min_value()
|
||||
} else {
|
||||
let unsigned_inner = n.value as $inner_type;
|
||||
if unsigned_inner as u128 != n.value || (unsigned_inner > 0) != (n.value > 0) {
|
||||
return None
|
||||
};
|
||||
if n.negative {
|
||||
match unsigned_inner.checked_neg() {
|
||||
Some(v) => v,
|
||||
None => return None,
|
||||
}
|
||||
} else {
|
||||
unsigned_inner
|
||||
}
|
||||
};
|
||||
Some(Self(inner))
|
||||
}
|
||||
|
||||
/// Calculate an approximation of a rational.
|
||||
///
|
||||
/// Result will be rounded to the nearest representable value, rounding down if it is
|
||||
/// equidistant between two neighbours.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn from_rational(a: u128, b: u128) -> Self {
|
||||
Self::from_rational_with_rounding(a, b, Rounding::NearestPrefDown)
|
||||
}
|
||||
|
||||
/// Calculate an approximation of a rational with custom rounding.
|
||||
///
|
||||
/// WARNING: This is a `const` function designed for convenient use at build time and
|
||||
/// will panic on overflow. Ensure that any inputs are sensible.
|
||||
pub const fn from_rational_with_rounding(a: u128, b: u128, rounding: Rounding) -> Self {
|
||||
if b == 0 {
|
||||
panic!("attempt to divide by zero in from_rational")
|
||||
}
|
||||
match multiply_by_rational_with_rounding(Self::DIV as u128, a, b, rounding) {
|
||||
Some(value) => match Self::from_i129(I129 { value, negative: false }) {
|
||||
Some(x) => x,
|
||||
None => panic!("overflow in from_rational"),
|
||||
},
|
||||
None => panic!("overflow in from_rational"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiply by another value, returning `None` in the case of an error.
|
||||
///
|
||||
/// Result will be rounded to the nearest representable value, rounding down if it is
|
||||
/// equidistant between two neighbours.
|
||||
pub const fn const_checked_mul(self, other: Self) -> Option<Self> {
|
||||
self.const_checked_mul_with_rounding(other, SignedRounding::NearestPrefLow)
|
||||
}
|
||||
|
||||
/// Multiply by another value with custom rounding, returning `None` in the case of an
|
||||
/// error.
|
||||
///
|
||||
/// Result will be rounded to the nearest representable value, rounding down if it is
|
||||
/// equidistant between two neighbours.
|
||||
pub const fn const_checked_mul_with_rounding(
|
||||
self,
|
||||
other: Self,
|
||||
rounding: SignedRounding,
|
||||
) -> Option<Self> {
|
||||
let lhs = self.into_i129();
|
||||
let rhs = other.into_i129();
|
||||
let negative = lhs.negative != rhs.negative;
|
||||
|
||||
match multiply_by_rational_with_rounding(
|
||||
lhs.value,
|
||||
rhs.value,
|
||||
Self::DIV as u128,
|
||||
Rounding::from_signed(rounding, negative),
|
||||
) {
|
||||
Some(value) => Self::from_i129(I129 { value, negative }),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Divide by another value, returning `None` in the case of an error.
|
||||
///
|
||||
/// Result will be rounded to the nearest representable value, rounding down if it is
|
||||
/// equidistant between two neighbours.
|
||||
pub const fn const_checked_div(self, other: Self) -> Option<Self> {
|
||||
self.checked_rounding_div(other, SignedRounding::NearestPrefLow)
|
||||
}
|
||||
|
||||
/// Divide by another value with custom rounding, returning `None` in the case of an
|
||||
/// error.
|
||||
///
|
||||
/// Result will be rounded to the nearest representable value, rounding down if it is
|
||||
/// equidistant between two neighbours.
|
||||
pub const fn checked_rounding_div(
|
||||
self,
|
||||
other: Self,
|
||||
rounding: SignedRounding,
|
||||
) -> Option<Self> {
|
||||
if other.0 == 0 {
|
||||
return None
|
||||
}
|
||||
|
||||
let lhs = self.into_i129();
|
||||
let rhs = other.into_i129();
|
||||
let negative = lhs.negative != rhs.negative;
|
||||
|
||||
match multiply_by_rational_with_rounding(
|
||||
lhs.value,
|
||||
Self::DIV as u128,
|
||||
rhs.value,
|
||||
Rounding::from_signed(rounding, negative),
|
||||
) {
|
||||
Some(value) => Self::from_i129(I129 { value, negative }),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Saturating for $name {
|
||||
@@ -522,6 +828,10 @@ macro_rules! implement_fixed {
|
||||
let rhs: I129 = other.0.into();
|
||||
let negative = lhs.negative != rhs.negative;
|
||||
|
||||
// Note that this uses the old (well-tested) code with sign-ignorant rounding. This
|
||||
// is equivalent to the `SignedRounding::NearestPrefMinor`. This means it is
|
||||
// expected to give exactly the same result as `const_checked_div` when the result
|
||||
// is positive and a result up to one epsilon greater when it is negative.
|
||||
multiply_by_rational(lhs.value, Self::DIV as u128, rhs.value)
|
||||
.ok()
|
||||
.and_then(|value| from_i129(I129 { value, negative }))
|
||||
@@ -851,6 +1161,16 @@ macro_rules! implement_fixed {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn op_sqrt_works() {
|
||||
for i in 1..1_000i64 {
|
||||
let x = $name::saturating_from_rational(i, 1_000i64);
|
||||
assert_eq!((x * x).try_sqrt(), Some(x));
|
||||
let x = $name::saturating_from_rational(i, 1i64);
|
||||
assert_eq!((x * x).try_sqrt(), Some(x));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn op_div_works() {
|
||||
let a = $name::saturating_from_integer(42);
|
||||
@@ -1133,6 +1453,41 @@ macro_rules! implement_fixed {
|
||||
assert_eq!(a.into_inner(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_rational_works() {
|
||||
let inner_max: u128 = <$name as FixedPointNumber>::Inner::max_value() as u128;
|
||||
let inner_min: u128 = 0;
|
||||
let accuracy: u128 = $name::accuracy() as u128;
|
||||
|
||||
// Max - 1.
|
||||
let a = $name::from_rational(inner_max - 1, accuracy);
|
||||
assert_eq!(a.into_inner() as u128, inner_max - 1);
|
||||
|
||||
// Min + 1.
|
||||
let a = $name::from_rational(inner_min + 1, accuracy);
|
||||
assert_eq!(a.into_inner() as u128, inner_min + 1);
|
||||
|
||||
// Max.
|
||||
let a = $name::from_rational(inner_max, accuracy);
|
||||
assert_eq!(a.into_inner() as u128, inner_max);
|
||||
|
||||
// Min.
|
||||
let a = $name::from_rational(inner_min, accuracy);
|
||||
assert_eq!(a.into_inner() as u128, inner_min);
|
||||
|
||||
let a = $name::from_rational(inner_max, 3 * accuracy);
|
||||
assert_eq!(a.into_inner() as u128, inner_max / 3);
|
||||
|
||||
let a = $name::from_rational(1, accuracy);
|
||||
assert_eq!(a.into_inner() as u128, 1);
|
||||
|
||||
let a = $name::from_rational(1, accuracy + 1);
|
||||
assert_eq!(a.into_inner() as u128, 1);
|
||||
|
||||
let a = $name::from_rational_with_rounding(1, accuracy + 1, Rounding::Down);
|
||||
assert_eq!(a.into_inner() as u128, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_mul_int_works() {
|
||||
let a = $name::saturating_from_integer(2);
|
||||
@@ -1272,6 +1627,76 @@ macro_rules! implement_fixed {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_checked_mul_works() {
|
||||
let inner_max = <$name as FixedPointNumber>::Inner::max_value();
|
||||
let inner_min = <$name as FixedPointNumber>::Inner::min_value();
|
||||
|
||||
let a = $name::saturating_from_integer(2u32);
|
||||
|
||||
// Max - 1.
|
||||
let b = $name::from_inner(inner_max - 1);
|
||||
assert_eq!(a.const_checked_mul((b / 2.into())), Some(b));
|
||||
|
||||
// Max.
|
||||
let c = $name::from_inner(inner_max);
|
||||
assert_eq!(a.const_checked_mul((c / 2.into())), Some(b));
|
||||
|
||||
// Max + 1 => None.
|
||||
let e = $name::from_inner(1);
|
||||
assert_eq!(a.const_checked_mul((c / 2.into() + e)), None);
|
||||
|
||||
if $name::SIGNED {
|
||||
// Min + 1.
|
||||
let b = $name::from_inner(inner_min + 1) / 2.into();
|
||||
let c = $name::from_inner(inner_min + 2);
|
||||
assert_eq!(a.const_checked_mul(b), Some(c));
|
||||
|
||||
// Min.
|
||||
let b = $name::from_inner(inner_min) / 2.into();
|
||||
let c = $name::from_inner(inner_min);
|
||||
assert_eq!(a.const_checked_mul(b), Some(c));
|
||||
|
||||
// Min - 1 => None.
|
||||
let b = $name::from_inner(inner_min) / 2.into() - $name::from_inner(1);
|
||||
assert_eq!(a.const_checked_mul(b), None);
|
||||
|
||||
let b = $name::saturating_from_rational(1i32, -2i32);
|
||||
let c = $name::saturating_from_integer(-21i32);
|
||||
let d = $name::saturating_from_integer(42);
|
||||
|
||||
assert_eq!(b.const_checked_mul(d), Some(c));
|
||||
|
||||
let minus_two = $name::saturating_from_integer(-2i32);
|
||||
assert_eq!(
|
||||
b.const_checked_mul($name::max_value()),
|
||||
$name::max_value().const_checked_div(minus_two)
|
||||
);
|
||||
assert_eq!(
|
||||
b.const_checked_mul($name::min_value()),
|
||||
$name::min_value().const_checked_div(minus_two)
|
||||
);
|
||||
|
||||
let c = $name::saturating_from_integer(255u32);
|
||||
assert_eq!(c.const_checked_mul($name::min_value()), None);
|
||||
}
|
||||
|
||||
let a = $name::saturating_from_rational(1i32, 2i32);
|
||||
let c = $name::saturating_from_integer(255i32);
|
||||
|
||||
assert_eq!(a.const_checked_mul(42.into()), Some(21.into()));
|
||||
assert_eq!(c.const_checked_mul(2.into()), Some(510.into()));
|
||||
assert_eq!(c.const_checked_mul($name::max_value()), None);
|
||||
assert_eq!(
|
||||
a.const_checked_mul($name::max_value()),
|
||||
$name::max_value().checked_div(&2.into())
|
||||
);
|
||||
assert_eq!(
|
||||
a.const_checked_mul($name::min_value()),
|
||||
$name::min_value().const_checked_div($name::saturating_from_integer(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_div_int_works() {
|
||||
let inner_max = <$name as FixedPointNumber>::Inner::max_value();
|
||||
|
||||
Reference in New Issue
Block a user