mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 16:57:58 +00:00
Defensive Programming in Substrate Reference Document (#2615)
_This PR is being continued from https://github.com/paritytech/polkadot-sdk/pull/2206, which was closed when the developer_hub was merged._ closes https://github.com/paritytech/polkadot-sdk-docs/issues/44 --- # Description This PR adds a reference document to the `developer-hub` crate (see https://github.com/paritytech/polkadot-sdk/pull/2102). This specific reference document covers defensive programming practices common within the context of developing a runtime with Substrate. In particular, this covers the following areas: - Default behavior of how Rust deals with numbers in general - How to deal with floating point numbers in runtime / fixed point arithmetic - How to deal with Integer overflows - General "safe math" / defensive programming practices for common pallet development scenarios - Defensive traits that exist within Substrate, i.e., `defensive_saturating_add `, `defensive_unwrap_or` - More general defensive programming examples (keep it concise) - Link to relevant examples where these practices are actually in production / being used - Unwrapping (or rather lack thereof) 101 todo -- - [x] Apply feedback from previous PR - [x] This may warrant a PR to append some of these docs to `sp_arithmetic` --------- Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> Co-authored-by: Radha <86818441+DrW3RK@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,33 @@
|
||||
// limitations under the License.
|
||||
|
||||
//! Decimal Fixed Point implementations for Substrate runtime.
|
||||
//! Similar to types that implement [`PerThing`](crate::per_things), these are also
|
||||
//! fixed-point types, however, they are able to represent larger fractions:
|
||||
#![doc = docify::embed!("./src/lib.rs", fixed_u64)]
|
||||
//!
|
||||
//! ### Fixed Point Types in Practice
|
||||
//!
|
||||
//! If one needs to exceed the value of one (1), then
|
||||
//! [`FixedU64`](FixedU64) (and its signed and `u128` counterparts) can be utilized.
|
||||
//! Take for example this very rudimentary pricing mechanism, where we wish to calculate the demand
|
||||
//! / supply to get a price for some on-chain compute:
|
||||
#![doc = docify::embed!(
|
||||
"./src/lib.rs",
|
||||
fixed_u64_block_computation_example
|
||||
)]
|
||||
//!
|
||||
//! For a much more comprehensive example, be sure to look at the source for broker (the "coretime")
|
||||
//! pallet.
|
||||
//!
|
||||
//! #### Fixed Point Types in Practice
|
||||
//!
|
||||
//! Just as with [`PerThing`](PerThing), you can also perform regular mathematical
|
||||
//! expressions:
|
||||
#![doc = docify::embed!(
|
||||
"./src/lib.rs",
|
||||
fixed_u64_operation_example
|
||||
)]
|
||||
//!
|
||||
|
||||
use crate::{
|
||||
helpers_128bit::{multiply_by_rational_with_rounding, sqrt},
|
||||
|
||||
@@ -101,7 +101,7 @@ where
|
||||
fn tcmp(&self, other: &T, threshold: T) -> Ordering {
|
||||
// early exit.
|
||||
if threshold.is_zero() {
|
||||
return self.cmp(other)
|
||||
return self.cmp(other);
|
||||
}
|
||||
|
||||
let upper_bound = other.saturating_add(threshold);
|
||||
@@ -206,12 +206,12 @@ where
|
||||
|
||||
// Nothing to do here.
|
||||
if count.is_zero() {
|
||||
return Ok(Vec::<T>::new())
|
||||
return Ok(Vec::<T>::new());
|
||||
}
|
||||
|
||||
let diff = targeted_sum.max(sum) - targeted_sum.min(sum);
|
||||
if diff.is_zero() {
|
||||
return Ok(input.to_vec())
|
||||
return Ok(input.to_vec());
|
||||
}
|
||||
|
||||
let needs_bump = targeted_sum > sum;
|
||||
@@ -254,7 +254,7 @@ where
|
||||
min_index += 1;
|
||||
min_index %= count;
|
||||
}
|
||||
leftover -= One::one()
|
||||
leftover -= One::one();
|
||||
}
|
||||
} else {
|
||||
// must decrease the stakes a bit. decrement from the max element. index of maximum is now
|
||||
@@ -288,7 +288,7 @@ where
|
||||
if output_with_idx[max_index].1 <= threshold {
|
||||
max_index = max_index.checked_sub(1).unwrap_or(count - 1);
|
||||
}
|
||||
leftover -= One::one()
|
||||
leftover -= One::one();
|
||||
} else {
|
||||
max_index = max_index.checked_sub(1).unwrap_or(count - 1);
|
||||
}
|
||||
@@ -300,7 +300,7 @@ where
|
||||
targeted_sum,
|
||||
"sum({:?}) != {:?}",
|
||||
output_with_idx,
|
||||
targeted_sum,
|
||||
targeted_sum
|
||||
);
|
||||
|
||||
// sort again based on the original index.
|
||||
@@ -356,7 +356,7 @@ mod normalize_tests {
|
||||
vec![
|
||||
Perbill::from_parts(333333334),
|
||||
Perbill::from_parts(333333333),
|
||||
Perbill::from_parts(333333333),
|
||||
Perbill::from_parts(333333333)
|
||||
]
|
||||
);
|
||||
|
||||
@@ -367,7 +367,7 @@ mod normalize_tests {
|
||||
vec![
|
||||
Perbill::from_parts(316666668),
|
||||
Perbill::from_parts(383333332),
|
||||
Perbill::from_parts(300000000),
|
||||
Perbill::from_parts(300000000)
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -378,13 +378,13 @@ mod normalize_tests {
|
||||
// could have a situation where the sum cannot be calculated in the inner type. Calculating
|
||||
// using the upper type of the per_thing should assure this to be okay.
|
||||
assert_eq!(
|
||||
vec![PerU16::from_percent(40), PerU16::from_percent(40), PerU16::from_percent(40),]
|
||||
vec![PerU16::from_percent(40), PerU16::from_percent(40), PerU16::from_percent(40)]
|
||||
.normalize(PerU16::one())
|
||||
.unwrap(),
|
||||
vec![
|
||||
PerU16::from_parts(21845), // 33%
|
||||
PerU16::from_parts(21845), // 33%
|
||||
PerU16::from_parts(21845), // 33%
|
||||
PerU16::from_parts(21845) // 33%
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -428,6 +428,88 @@ mod normalize_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod per_and_fixed_examples {
|
||||
use super::*;
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn percent_mult() {
|
||||
let percent = Percent::from_rational(5u32, 100u32); // aka, 5%
|
||||
let five_percent_of_100 = percent * 100u32; // 5% of 100 is 5.
|
||||
assert_eq!(five_percent_of_100, 5)
|
||||
}
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn perbill_example() {
|
||||
let p = Perbill::from_percent(80);
|
||||
// 800000000 bil, or a representative of 0.800000000.
|
||||
// Precision is in the billions place.
|
||||
assert_eq!(p.deconstruct(), 800000000);
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn percent_example() {
|
||||
let percent = Percent::from_rational(190u32, 400u32);
|
||||
assert_eq!(percent.deconstruct(), 47);
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn fixed_u64_block_computation_example() {
|
||||
// Calculate a very rudimentary on-chain price from supply / demand
|
||||
// Supply: Cores available per block
|
||||
// Demand: Cores being ordered per block
|
||||
let price = FixedU64::from_rational(5u128, 10u128);
|
||||
|
||||
// 0.5 DOT per core
|
||||
assert_eq!(price, FixedU64::from_float(0.5));
|
||||
|
||||
// Now, the story has changed - lots of demand means we buy as many cores as there
|
||||
// available. This also means that price goes up! For the sake of simplicity, we don't care
|
||||
// about who gets a core - just about our very simple price model
|
||||
|
||||
// Calculate a very rudimentary on-chain price from supply / demand
|
||||
// Supply: Cores available per block
|
||||
// Demand: Cores being ordered per block
|
||||
let price = FixedU64::from_rational(19u128, 10u128);
|
||||
|
||||
// 1.9 DOT per core
|
||||
assert_eq!(price, FixedU64::from_float(1.9));
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn fixed_u64() {
|
||||
// The difference between this and perthings is perthings operates within the relam of [0,
|
||||
// 1] In cases where we need > 1, we can used fixed types such as FixedU64
|
||||
|
||||
let rational_1 = FixedU64::from_rational(10, 5); //" 200%" aka 2.
|
||||
let rational_2 = FixedU64::from_rational_with_rounding(5, 10, Rounding::Down); // "50%" aka 0.50...
|
||||
|
||||
assert_eq!(rational_1, (2u64).into());
|
||||
assert_eq!(rational_2.into_perbill(), Perbill::from_float(0.5));
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn fixed_u64_operation_example() {
|
||||
let rational_1 = FixedU64::from_rational(10, 5); // "200%" aka 2.
|
||||
let rational_2 = FixedU64::from_rational(8, 5); // "160%" aka 1.6.
|
||||
|
||||
let addition = rational_1 + rational_2;
|
||||
let multiplication = rational_1 * rational_2;
|
||||
let division = rational_1 / rational_2;
|
||||
let subtraction = rational_1 - rational_2;
|
||||
|
||||
assert_eq!(addition, FixedU64::from_float(3.6));
|
||||
assert_eq!(multiplication, FixedU64::from_float(3.2));
|
||||
assert_eq!(division, FixedU64::from_float(1.25));
|
||||
assert_eq!(subtraction, FixedU64::from_float(0.4));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod threshold_compare_tests {
|
||||
use super::*;
|
||||
@@ -440,15 +522,15 @@ mod threshold_compare_tests {
|
||||
let e = Perbill::from_percent(10).mul_ceil(b);
|
||||
|
||||
// [115 - 11,5 (103,5), 115 + 11,5 (126,5)] is all equal
|
||||
assert_eq!(103u32.tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!(104u32.tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!(115u32.tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!(120u32.tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!(126u32.tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!(127u32.tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!((103u32).tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!((104u32).tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!((115u32).tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!((120u32).tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!((126u32).tcmp(&b, e), Ordering::Equal);
|
||||
assert_eq!((127u32).tcmp(&b, e), Ordering::Equal);
|
||||
|
||||
assert_eq!(128u32.tcmp(&b, e), Ordering::Greater);
|
||||
assert_eq!(102u32.tcmp(&b, e), Ordering::Less);
|
||||
assert_eq!((128u32).tcmp(&b, e), Ordering::Greater);
|
||||
assert_eq!((102u32).tcmp(&b, e), Ordering::Less);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -458,15 +540,15 @@ mod threshold_compare_tests {
|
||||
let e = Perbill::from_parts(100) * b;
|
||||
|
||||
// [115 - 11,5 (103,5), 115 + 11,5 (126,5)] is all equal
|
||||
assert_eq!(103u32.tcmp(&b, e), 103u32.cmp(&b));
|
||||
assert_eq!(104u32.tcmp(&b, e), 104u32.cmp(&b));
|
||||
assert_eq!(115u32.tcmp(&b, e), 115u32.cmp(&b));
|
||||
assert_eq!(120u32.tcmp(&b, e), 120u32.cmp(&b));
|
||||
assert_eq!(126u32.tcmp(&b, e), 126u32.cmp(&b));
|
||||
assert_eq!(127u32.tcmp(&b, e), 127u32.cmp(&b));
|
||||
assert_eq!((103u32).tcmp(&b, e), (103u32).cmp(&b));
|
||||
assert_eq!((104u32).tcmp(&b, e), (104u32).cmp(&b));
|
||||
assert_eq!((115u32).tcmp(&b, e), (115u32).cmp(&b));
|
||||
assert_eq!((120u32).tcmp(&b, e), (120u32).cmp(&b));
|
||||
assert_eq!((126u32).tcmp(&b, e), (126u32).cmp(&b));
|
||||
assert_eq!((127u32).tcmp(&b, e), (127u32).cmp(&b));
|
||||
|
||||
assert_eq!(128u32.tcmp(&b, e), 128u32.cmp(&b));
|
||||
assert_eq!(102u32.tcmp(&b, e), 102u32.cmp(&b));
|
||||
assert_eq!((128u32).tcmp(&b, e), (128u32).cmp(&b));
|
||||
assert_eq!((102u32).tcmp(&b, e), (102u32).cmp(&b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,6 +15,42 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Types that implement [`PerThing`](PerThing) can be used as a floating-point alternative for
|
||||
//! numbers that operate within the realm of `[0, 1]`. The primary types may you encounter in
|
||||
//! Substrate would be the following:
|
||||
//! - [`Percent`](Percent) - parts of one hundred.
|
||||
//! - [`Permill`](Permill) - parts of a million.
|
||||
//! - [`Perbill`](Perbill) - parts of a billion.
|
||||
//!
|
||||
//! In use, you may see them being used as follows:
|
||||
//!
|
||||
//! > **[`Perbill`](Perbill), parts of a billion**
|
||||
#![doc = docify::embed!("./src/lib.rs", perbill_example)]
|
||||
//! > **[`Percent`](Percent), parts of a hundred**
|
||||
#![doc = docify::embed!("./src/lib.rs", percent_example)]
|
||||
//!
|
||||
//! Note that `Percent` is represented as a _rounded down_, fixed point
|
||||
//! number (see the example above). Unlike primitive types, types that implement
|
||||
//! [`PerThing`](PerThing) will also not overflow, and are therefore safe to use.
|
||||
//! They adopt the same behavior that a saturated calculation would provide, meaning that if one is
|
||||
//! to go over "100%", it wouldn't overflow, but simply stop at the upper or lower bound.
|
||||
//!
|
||||
//! For use cases which require precision beyond the range of `[0, 1]`, there are fixed-point types
|
||||
//! which can be used.
|
||||
//!
|
||||
//! Each of these can be used to construct and represent ratios within our runtime.
|
||||
//! You will find types like [`Perbill`](Perbill) being used often in pallet
|
||||
//! development. `pallet_referenda` is a good example of a pallet which makes good use of fixed
|
||||
//! point arithmetic, as it relies on representing various curves and thresholds relating to
|
||||
//! governance.
|
||||
//!
|
||||
//! #### Fixed Point Arithmetic with [`PerThing`](PerThing)
|
||||
//!
|
||||
//! As stated, one can also perform mathematics using these types directly. For example, finding the
|
||||
//! percentage of a particular item:
|
||||
|
||||
#![doc = docify::embed!("./src/lib.rs", percent_mult)]
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user