From c27d31eb3a5978a47edf3b05ffe8cab6bc020a89 Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Mon, 6 Nov 2023 16:50:51 -0500 Subject: [PATCH 01/29] initial draft --- .../safe_defensive_programming.rs | 405 ++++++++++++++++++ 1 file changed, 405 insertions(+) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 9d0f028e570d..b860dd41e3f3 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -1 +1,406 @@ +//! As our runtime should _never_ panic; this includes eliminating the possibility of integer overflows, +//! converting between number types, or even handling floating point usage with fixed point arithmetic +//! to mitigate issues that come with floating point calculations. //! +//! ## Integer Overflow +//! +//! The Rust compiler prevents any sort of static overflow from happening at compile time, for example: +//! +//! ```rust +//! let overflow = u8::MAX + 10; +//! +//! // Results in: +//! error: this arithmetic operation will overflow +//! --> src/main.rs:121:24 +//! | +//! 121 | let overflow = u8::MAX + 10; +//! | ^^^^^^^^^^^^ attempt to compute `u8::MAX + 10_u8`, which would overflow +//! | +//! ``` +//! +//! However in runtime, we don't always have control over what is being supplied as a parameter. For +//! example, this counter function could present one of two outcomes depending on whether it is in +//! **release** or **debug** mode: +//! +#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", naive_add)] +//! If we passed in overflow-able value sat runtime, this could actually panic (or wrap, if in release). +//! ```rust +//! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4. +//! ``` +//! +//! The Rust compiler would panic in **debug** mode in the event of an integer overflow. In **release** +//! mode, it resorts to silently _wrapping_ the overflowed amount in a modular fashion, (hence returning +//! `4`). +//! +//! It is actually the _silent_ portion of this behavior that presents a real issue - as it may be an +//! unintended, but also a very _silent killer_ in terms of producing bugs. In fact, it may have been +//! better for this type of behavior to produce some sort of error, or even `panic!`, as in that +//! scenario, at least such behavior could become obvious. Especially in the context of blockchain +//! development, where unsafe arithmetic could produce unexpected consequences. +//! +//! A quick example is a user's balance overflowing: the default behavior of wrapping could result in +//! the user's balance starting from zero, or vice versa, of a `0` balance turning into the `MAX` of +//! some type. Naturally, this could lead to various exploits and issues down the road, which if failing +//! silently, would be difficult to trace and rectify. +//! +//! Luckily, there are ways to both represent and handle these scenarios depending on our specific use +//! case natively built into Rust, as well as libraries like `sp_arithmetic`. +//! +//! ## Safe Math +//! +//! Our main objective is to reduce the likelihood of any unintended or undefined behavior within our +//! blockchain runtime. Intentional and predictable design should be our first and foremost property for +//! ensuring a well running, safely designed system. Both Rust and Substrate both provide safe ways to +//! deal with numbers and alternatives to floating point arithmetic. +//! +//! Defensive, or safe math, isn't just useful for blockchain development. +//! +//! Traditional banking also needs to utilize such practices within its codebase. Rather than use purely +//! primitive, native types, **fixed-point arithmetic** usually involves abstracting such operations +//! into more controlled, fixed-point types. +//! +//! Banking applications also don't use floating point numbers. Rather they (should) use fixed-point +//! arithmetic to mitigate the potential for inaccuracy, rounding errors, or other unexpected behavior. +//! For more on the specifics of why this is, +//! [watch this video by the Computerfile](https://www.youtube.com/watch?v=PZRI1IfStY0). +//! +//! Using **primitive** floating point number types in a blockchain context should also be avoided, as a +//! single nondeterministic result could cause chaos for consensus along with the aforementioned issues. +//! +//! +//! The following methods represent different ways one can handle numbers safely natively in Rust, +//! without fear of panic or unexpected behavior from wrapping. +//! +//! ### Checked Operations +//! +//! **Checked operations** utilize a `Option` as a return type. This means that if the resulting +//! operation is invalid, i.e., an integer overflow, it will return `None`, and if successful, then +//! `Some`. The only downside of using this type is the need to handle the `Option` type accordingly. +//! +//! This is an example of a valid operation: +//! +#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", checked_add_example)] +//! +//! This is an example of an invalid operation, in this case, a simulated integer overflow, which would +//! simply result in `None`: +//! +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + checked_add_handle_error_example +)] +//! +//! Typically, if you aren't sure about which operation to use for runtime math, **checked** operations +//! are a safe bet, as it presents two, predictable (and _erroring_) outcomes that can be handled +//! accordingly (`Some` and `None`). +//! +//! In a practical context, the resulting `Option` should be handled accordingly. The following +//! conventions can be seen from the within the Polkadot SDK, where an `Option` can be handled in one of +//! two ways: +//! +//! - As an `Option`, using the `if let` / `if` or `match` +//! - As a `Result`, via `ok_or` (or similar conversion to `Result` from `Option`) +//! +//! #### Handling via Option - More Verbose +//! +//! Because wrapped operations return `Option`, you can use a more verbose/explicit form of error +//! handling via `if` or `if let`: +//! +#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", increase_balance)] +//! +//! Optionally, `match` may also be directly used in a more concise manner: +//! +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + increase_balance_match +)] +//! +//! This is generally a useful convention for handling not only checked types, but most types that +//! return `Option`. +//! +//! #### Handling via Result - Less Verbose +//! +//! In the Polkadot SDK codebase, you may see checked operations being handled as a `Result` via +//! `ok_or`. This is a less verbose way of expressing the above. Which to use often boils down to +//! the developer's preference: +//! +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + increase_balance_result +)] +//! +//! At a glance, this may seem confusing, as we just got done explaining how to handle a `Option`, not +//! `Result`. `Result` may be used as an alternative to `Option` where it ergonomically makes sense to +//! let the user know that something unexpected has happened. This is particularly useful in the context +//! of dispatchables within a Substrate pallet, for example, where information about a failure to +//! perform some action matters in the context of the application. +//! +//! #### Should you use `ok_or` or `ok_or_else`? +//! +//! You may see `ok_or` and `ok_or_else` being used interchangeably. In reality, they have the same +//! functionality with one caveat - `ok_or` is _eagerly_ evaluated, versus `ok_or_else` is _lazily_ +//! evaluated. Using `ok_or_else` is more performant, as if the `Option` is `Some()`, there is no need +//! to actually run the closure. `ok_or` is eager to make new allocations - regardless of whether it is +//! relevant or not, thereby making it slightly more expensive. +//! +//! [See more here.](https://rust-lang.github.io/rust-clippy/master/index.html#/or_fun_call) +//! +//! +//! +//! ### Saturated Operations +//! +//! Saturating a number limits it to the type's upper or lower bound, no matter the integer would +//! overflow in runtime. For example, adding to `u32::MAX` would simply limit itself to `u32::MAX`: +//! +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + saturated_add_example +)] +//! +//! Saturating calculations can be used if one is very sure that something won't overflow, but wants to +//! avoid introducing the notion of any potential-panic or wrapping behavior. +//! +//! ### Mathematical Operations in Substrate Development - Further Context +//! +//! As a recap, we covered the following concepts: +//! +//! 1. **Checked** operations - using `Option` or `Result`, +//! 2. **Saturated** operations - limited to the lower and upper bounds of a number type, +//! 3. **Wrapped** operations (the default) - wrap around to above or below the bounds of a type, +//! +//! #### The problem with 'default' wrapped operations +//! +//! **Wrapped operations** cause the overflow to wrap around to either the maximum or minimum of that +//! type. Imagine this in the context of a blockchain, where there are balances, voting counters, nonces +//! for transactions, and other aspects of a blockchain. +//! +//! Some of these mechanisms can be more critical than others. It's for this reason that we may consider +//! some other ways of dealing with runtime arithmetic, such as saturated or checked operations, that +//! won't carry these potential consequences. +//! +//! +//! While it may seem trivial, choosing how to handle numbers is quite important. As a thought exercise, +//! here are some scenarios of which will shed more light on when to use which. +//! +//! #### Bob's Overflowed Balance +//! +//! **Bob's** balance exceeds the `Balance` type on the `EduChain`. Because the pallet developer did not +//! handle the calculation to add to Bob's balance with any regard to this overflow, **Bob's** balance +//! is now essentially `0`, the operation **wrapped**. +//! +//!
+//! Solution: Saturating or Checked +//! For Bob's balance problems, using a saturated_add or checked_add could've mitigated this issue. They simply would've reached the upper, or lower bounds, of the particular type for an on-chain balance. In other words: Bob's balance would've stayed at the maximum of the Balance type. +//!
+//! +//! #### Alice's 'Underflowed' Balance +//! +//! **Alice's** balance has reached `0` after a transfer to `Bob`. Suddenly, she has been slashed on +//! `EduChain`, causing her balance to reach near the limit of `u32::MAX` - a very large amount - as +//! _wrapped operations_ can go both ways. **Alice** can now successfully vote using her new, +//! overpowered token balance, destroying the integrity of the chain. +//! +//!
+//! Solution: Saturating +//! For Alice's balance problem, using saturated_sub could've mitigated this issue. As debt or having a negative balance is not a concept within blockchains, a saturating calculation would've simply limited her balance to the lower bound of u32. +//! +//! In other words: Alice's balance would've stayed at "0", even after being slashed. +//! +//! This is also an example that while one system may work in isolation, shared interfaces, such as the notion of balances, are often shared across multiple pallets - meaning these small changes can make a big difference in outcome. +//!
+//! +//! #### Proposals' ID Overwrite +//! +//! The type for counting the number of proposals on-chain is represented by a `u8` number, called +//! `proposals_count`. Every time a new proposal is added to the system, this number increases. With the +//! proposal pallet being high in usage, it has reached `u8::MAX`'s limit of `255`, causing +//! `proposals_count` to go to `0`. Unfortunately, this resulted in new proposals overwriting old ones, +//! effectively erasing any notion of past proposals! +//! +//!
+//! Solution: Checked +//! For the proposal IDs, proper handling via `checked` math would've been much more suitable, Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to ensure that the next proposal ID would've been valid would've been a viable way to let the user know the state of their proposal: +//! +//! ```rust +//! let next_proposal_id = current_count.checked_add(1).ok_or_else(|| Error::TooManyProposals)?; +//! ``` +//! +//!
+//! +//! --- +//! +//! From the above, we can clearly see the problematic nature of seemingly simple operations in runtime. +//! Of course, it may be that using checked math is perfectly fine under some scenarios - such as +//! certain balance being never realistically attainable, or a number type being so large that it could +//! never realistically overflow unless one sent thousands of transactions to the network. +//! +//! ### Decision Chart: When to use which? +//! +#![doc = simple_mermaid::mermaid!("../../../docs/mermaid/integer_operation_decision.mmd")] +//! ## Fixed Point Arithmetic +//! +//! The following code may use types from [`sp_arithmetic`]. Feel free to follow along by including +//! these crates. +//! +//! Fixed point arithmetic solves the aforementioned problems of dealing with the (sometimes) uncertain +//! nature of floating point numbers. Rather than use a radix point (`0.80`), a type which _represents_ +//! a floating point number in base 10 can be used instead. +//! +//! **Example - [`Perbill`], parts of a billion** +//! +#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", perbill_example)] +//! +//! **Example - [`Percent`](sp_arithmetic::Percent), parts of a hundred** +//! +#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_example)] +//! +//! Note that `190 / 400 = 0.475`, and that `Percent` represents it as a _rounded down_, fixed point +//! number (`47`). Unlike primitive types, types that implement `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. +//! +//! ### Using 'PerThing' +//! +//! [`sp_arithmetic`] contains a trait called [`PerThing`](sp_arithmetic::PerThing), allowing a custom type to be implemented specifically for fixed point arithmetic. While a number of fixed-point types are introduced, let's focus on a few specific examples that implement `PerThing`: +//! +//! - [`Percent`](sp_arithmetic::Percent) - parts of one hundred. +//! - [`Permill`](sp_arithmetic::Permill) - parts of a million. +//! - [`Perbill`](sp_arithmetic::Perbill)] - parts of a billion. +//! +//! Because each of these implement the same trait, `PerThing`, we have access to a few widely used +//! methods: +//! +//! - [`from_rational()`](sp_arithmetic::PerThing::from_rational()) +//! - [`from_percent()`](sp_arithmetic::PerThing::from_percent()) +//! - [`from_parts()`](sp_arithmetic::PerThing::from_parts()) +//! +//! Each of these can be used to construct and represent ratios within our runtime. +//! +//! #### Fixed Point Arithmetic with [`PerThing`](sp_arithmetic::PerThing) +//! +//! As stated, one can also perform mathematics using these types directly. For example, finding the +//! percentage of a particular item via multiplication: +//! +#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_mult)] +//! +//! With the knowledge of how these types operate in relation to other numbers, let's explore how +//! they're used in Substrate development. +//! +//! ### Fixed Point Math in Substrate Development - Further Context +//! +//! +//! Let's examine the usage of `Perbill` and how exactly we can use it as an alternative to floating +//! point numbers in Substrate development. For this scenario, let's say we are demonstrating a _voting_ +//! system which depends on reaching a certain threshold, or percentage, before it can be deemed valid. +//! +//! For most cases, `Perbill` gives us a reasonable amount of precision for most applications, which is +//! why we're using it here. +//! +//! Later, in the context of a FRAME pallet, the usage of these types and calculations will start to +//! make more sense when dealing with mathematics in the runtime. + +#[docify::export] +pub mod overflow_examples { + + enum BlockchainError { + Overflow, + } + + type Address = &'static str; + + struct Runtime; + + impl Runtime { + fn get_balance(account: Address) -> u64 { + 0 + } + } + + #[docify::export] + fn naive_add(x: u8, y: u8) -> u8 { + x + y + } + + #[docify::export] + fn checked_add_example() { + // This is valid, as 20 is perfectly within the bounds of u32. + let add = (10u32).checked_add(10); + assert_eq!(add, Some(20)) + } + + #[docify::export] + fn checked_add_handle_error_example() { + // This is invalid - we are adding something to the max of u32::MAX, which would overflow. + // Luckily, checked_add just marks this as None! + let add = u32::MAX.checked_add(10); + assert_eq!(add, None) + } + + #[docify::export] + fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + if let Some(new_balance) = balance.checked_add(amount) { + Runtime::set_balance(account, new_balance); + return Ok(()); + } else { + return Err(BlockchainError::Overflow); + } + } + + #[docify::export] + fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + let new_balance = match balance.checked_add(amount) { + Some(balance) => balance, + None => { + return Err(BlockchainError::Overflow); + } + }; + Runtime::set_balance(account, new_balance); + Ok(()) + } + + #[docify::export] + fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount - this time, by using `ok_or` + let new_balance = balance.checked_add(amount).ok_or_else(|| BlockchainError::Overflow)?; + Runtime::set_balance(account, new_balance); + Ok(()) + } + + #[docify::export] + fn saturated_add_example() { + // Saturating add simply 'saturates' to the numeric bound of that type if it overflows. + let add = u32::MAX.saturating_add(10); + assert_eq!(add, u32::MAX) + } +} + +pub mod perthing_examples { + // TODO: Add real types, real examples + + #[docify::export] + 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] + 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] + fn percent_example() { + let percent = Percent::from_rational(190u32, 400u32); + assert_eq!(percent.deconstruct(), 47); + } +} From 7579a4e3ab8217dc7e01a5198f437caba338f71c Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Tue, 7 Nov 2023 12:19:37 -0500 Subject: [PATCH 02/29] minor changes + fix stuff --- Cargo.lock | 3 ++ developer-hub/Cargo.toml | 4 ++ .../safe_defensive_programming.rs | 52 +++++++++---------- docs/mermaid/integer_operation_decision.mmd | 7 +++ 4 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 docs/mermaid/integer_operation_decision.mmd diff --git a/Cargo.lock b/Cargo.lock index 7c71cfde3264..b71b7a29b961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4634,6 +4634,7 @@ dependencies = [ "pallet-aura", "pallet-default-config-example", "pallet-examples", + "pallet-referenda", "pallet-timestamp", "parity-scale-codec", "sc-cli", @@ -4650,6 +4651,7 @@ dependencies = [ "scale-info", "simple-mermaid 0.1.0 (git+https://github.com/kianenigma/simple-mermaid.git?branch=main)", "sp-api", + "sp-arithmetic", "sp-core", "sp-io", "sp-keyring", @@ -10548,6 +10550,7 @@ name = "pallet-referenda" version = "4.0.0-dev" dependencies = [ "assert_matches", + "docify", "frame-benchmarking", "frame-support", "frame-system", diff --git a/developer-hub/Cargo.toml b/developer-hub/Cargo.toml index dac6c4ad500e..970733706dd2 100644 --- a/developer-hub/Cargo.toml +++ b/developer-hub/Cargo.toml @@ -18,6 +18,9 @@ frame = { path = "../substrate/frame", features = ["runtime", "experimental"] } pallet-examples = { path = "../substrate/frame/examples" } pallet-default-config-example = { path = "../substrate/frame/examples/default-config" } +# Extra example pallets +pallet-referenda = { path = "../substrate/frame/referenda" } + # How we build docs in rust-docs simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", branch = "main" } docify = "0.2.6" @@ -57,6 +60,7 @@ sp-api = { path = "../substrate/primitives/api" } sp-core = { path = "../substrate/primitives/core" } sp-keyring = { path = "../substrate/primitives/keyring" } sp-runtime = { path = "../substrate/primitives/runtime" } +sp-arithmetic = { path = "../substrate/primitives/arithmetic" } [dev-dependencies] parity-scale-codec = "3.6.5" diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index b860dd41e3f3..f599fcfc3e85 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -6,9 +6,10 @@ //! //! The Rust compiler prevents any sort of static overflow from happening at compile time, for example: //! -//! ```rust +//! ```ignore //! let overflow = u8::MAX + 10; -//! +//! ``` +//! ```text //! // Results in: //! error: this arithmetic operation will overflow //! --> src/main.rs:121:24 @@ -18,13 +19,15 @@ //! | //! ``` //! +//! //! However in runtime, we don't always have control over what is being supplied as a parameter. For //! example, this counter function could present one of two outcomes depending on whether it is in //! **release** or **debug** mode: //! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", naive_add)] +//! //! If we passed in overflow-able value sat runtime, this could actually panic (or wrap, if in release). -//! ```rust +//! ```ignore //! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4. //! ``` //! @@ -194,18 +197,18 @@ //! //! #### Alice's 'Underflowed' Balance //! -//! **Alice's** balance has reached `0` after a transfer to `Bob`. Suddenly, she has been slashed on -//! `EduChain`, causing her balance to reach near the limit of `u32::MAX` - a very large amount - as -//! _wrapped operations_ can go both ways. **Alice** can now successfully vote using her new, +//! Alice's balance has reached `0` after a transfer to Bob. Suddenly, she has been slashed on +//! EduChain, causing her balance to reach near the limit of `u32::MAX` - a very large amount - as +//! _wrapped operations_ can go both ways. Alice can now successfully vote using her new, //! overpowered token balance, destroying the integrity of the chain. //! //!
-//! Solution: Saturating -//! For Alice's balance problem, using saturated_sub could've mitigated this issue. As debt or having a negative balance is not a concept within blockchains, a saturating calculation would've simply limited her balance to the lower bound of u32. +//! Solution: Saturating +//! For Alice's balance problem, using saturated_sub could've mitigated this issue. As debt or having a negative balance is not a concept within blockchains, a saturating calculation would've simply limited her balance to the lower bound of u32. //! -//! In other words: Alice's balance would've stayed at "0", even after being slashed. +//! In other words: Alice's balance would've stayed at "0", even after being slashed. //! -//! This is also an example that while one system may work in isolation, shared interfaces, such as the notion of balances, are often shared across multiple pallets - meaning these small changes can make a big difference in outcome. +//! This is also an example that while one system may work in isolation, shared interfaces, such as the notion of balances, are often shared across multiple pallets - meaning these small changes can make a big difference in outcome. //!
//! //! #### Proposals' ID Overwrite @@ -220,13 +223,12 @@ //! Solution: Checked //! For the proposal IDs, proper handling via `checked` math would've been much more suitable, Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to ensure that the next proposal ID would've been valid would've been a viable way to let the user know the state of their proposal: //! -//! ```rust +//! ```ignore //! let next_proposal_id = current_count.checked_add(1).ok_or_else(|| Error::TooManyProposals)?; //! ``` //! //! //! -//! --- //! //! From the above, we can clearly see the problematic nature of seemingly simple operations in runtime. //! Of course, it may be that using checked math is perfectly fine under some scenarios - such as @@ -241,11 +243,11 @@ //! The following code may use types from [`sp_arithmetic`]. Feel free to follow along by including //! these crates. //! -//! Fixed point arithmetic solves the aforementioned problems of dealing with the (sometimes) uncertain +//! Fixed point arithmetic solves the aforementioned problems of dealing with the uncertain //! nature of floating point numbers. Rather than use a radix point (`0.80`), a type which _represents_ //! a floating point number in base 10 can be used instead. //! -//! **Example - [`Perbill`], parts of a billion** +//! **Example - [`Perbill`](sp_arithmetic::Perbill), parts of a billion** //! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", perbill_example)] //! @@ -259,13 +261,13 @@ //! meaning that if one is to go over "100%", it wouldn't overflow, but simply stop at the upper or //! lower bound. //! -//! ### Using 'PerThing' +//! ### Using 'PerThing' In Practice //! //! [`sp_arithmetic`] contains a trait called [`PerThing`](sp_arithmetic::PerThing), allowing a custom type to be implemented specifically for fixed point arithmetic. While a number of fixed-point types are introduced, let's focus on a few specific examples that implement `PerThing`: //! //! - [`Percent`](sp_arithmetic::Percent) - parts of one hundred. //! - [`Permill`](sp_arithmetic::Permill) - parts of a million. -//! - [`Perbill`](sp_arithmetic::Perbill)] - parts of a billion. +//! - [`Perbill`](sp_arithmetic::Perbill) - parts of a billion. //! //! Because each of these implement the same trait, `PerThing`, we have access to a few widely used //! methods: @@ -279,7 +281,7 @@ //! #### Fixed Point Arithmetic with [`PerThing`](sp_arithmetic::PerThing) //! //! As stated, one can also perform mathematics using these types directly. For example, finding the -//! percentage of a particular item via multiplication: +//! percentage of a particular item: //! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_mult)] //! @@ -288,20 +290,19 @@ //! //! ### Fixed Point Math in Substrate Development - Further Context //! +//! You will find types like [`Perbill`](sp_arithmetic::Perbill) being used often in pallet development. [`pallet_referenda`] is a good example of a pallet +//! which makes good use of fixed point arithmetic to calculate //! //! Let's examine the usage of `Perbill` and how exactly we can use it as an alternative to floating //! point numbers in Substrate development. For this scenario, let's say we are demonstrating a _voting_ //! system which depends on reaching a certain threshold, or percentage, before it can be deemed valid. +//! //! //! For most cases, `Perbill` gives us a reasonable amount of precision for most applications, which is //! why we're using it here. -//! -//! Later, in the context of a FRAME pallet, the usage of these types and calculations will start to -//! make more sense when dealing with mathematics in the runtime. - -#[docify::export] -pub mod overflow_examples { +#[cfg(test)] +mod tests { enum BlockchainError { Overflow, } @@ -380,11 +381,6 @@ pub mod overflow_examples { let add = u32::MAX.saturating_add(10); assert_eq!(add, u32::MAX) } -} - -pub mod perthing_examples { - // TODO: Add real types, real examples - #[docify::export] fn percent_mult() { let percent = Percent::from_rational(5u32, 100u32); // aka, 5% diff --git a/docs/mermaid/integer_operation_decision.mmd b/docs/mermaid/integer_operation_decision.mmd new file mode 100644 index 000000000000..8a73d5128bbd --- /dev/null +++ b/docs/mermaid/integer_operation_decision.mmd @@ -0,0 +1,7 @@ +flowchart LR + CH["Checked"] + ST["Saturated"] + CH-->NEED["The user needs to know that the operation failed - and why"] + CH-->DOUBT["Unsure whether this operation could fail/overflow"] + ST-->SILENT["Silently reaching upper/lower bound will not result in any damage"] + ST-->REASON["In all reasonable circumstances, the number will not overflow"] From 57c79316008d61977d425cfa6c802cf40c8a253a Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Tue, 7 Nov 2023 14:01:02 -0500 Subject: [PATCH 03/29] trim it down --- Cargo.lock | 1 - .../safe_defensive_programming.rs | 82 +++++++------------ docs/mermaid/integer_operation_decision.mmd | 2 +- 3 files changed, 29 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b71b7a29b961..d8418fc8c03e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10550,7 +10550,6 @@ name = "pallet-referenda" version = "4.0.0-dev" dependencies = [ "assert_matches", - "docify", "frame-benchmarking", "frame-support", "frame-system", diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index f599fcfc3e85..3114639b4797 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -9,8 +9,8 @@ //! ```ignore //! let overflow = u8::MAX + 10; //! ``` +//! Would yield the following error: //! ```text -//! // Results in: //! error: this arithmetic operation will overflow //! --> src/main.rs:121:24 //! | @@ -20,8 +20,8 @@ //! ``` //! //! -//! However in runtime, we don't always have control over what is being supplied as a parameter. For -//! example, this counter function could present one of two outcomes depending on whether it is in +//! However in the runtime context, we don't always have control over what is being supplied as a parameter. For +//! example, even this simple adding function could present one of two outcomes depending on whether it is in //! **release** or **debug** mode: //! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", naive_add)] @@ -36,7 +36,7 @@ //! `4`). //! //! It is actually the _silent_ portion of this behavior that presents a real issue - as it may be an -//! unintended, but also a very _silent killer_ in terms of producing bugs. In fact, it may have been +//! unintended, but also a very _silent killer_ in terms of producing bugs. In fact, it would have been //! better for this type of behavior to produce some sort of error, or even `panic!`, as in that //! scenario, at least such behavior could become obvious. Especially in the context of blockchain //! development, where unsafe arithmetic could produce unexpected consequences. @@ -47,38 +47,28 @@ //! silently, would be difficult to trace and rectify. //! //! Luckily, there are ways to both represent and handle these scenarios depending on our specific use -//! case natively built into Rust, as well as libraries like `sp_arithmetic`. +//! case natively built into Rust, as well as libraries like [`sp_arithmetic`]. //! -//! ## Safe Math +//! ## Infallible Arithmetic //! -//! Our main objective is to reduce the likelihood of any unintended or undefined behavior within our -//! blockchain runtime. Intentional and predictable design should be our first and foremost property for +//! Our main objective in runtime development is to reduce the likelihood of any *unintended* or *undefined* behavior. +//! Intentional and predictable design should be our first and foremost property for //! ensuring a well running, safely designed system. Both Rust and Substrate both provide safe ways to //! deal with numbers and alternatives to floating point arithmetic. //! -//! Defensive, or safe math, isn't just useful for blockchain development. -//! -//! Traditional banking also needs to utilize such practices within its codebase. Rather than use purely -//! primitive, native types, **fixed-point arithmetic** usually involves abstracting such operations -//! into more controlled, fixed-point types. -//! -//! Banking applications also don't use floating point numbers. Rather they (should) use fixed-point -//! arithmetic to mitigate the potential for inaccuracy, rounding errors, or other unexpected behavior. -//! For more on the specifics of why this is, -//! [watch this video by the Computerfile](https://www.youtube.com/watch?v=PZRI1IfStY0). +//! Rather they (should) use fixed-point arithmetic to mitigate the potential for inaccuracy, rounding errors, or other unexpected behavior. +//! For more on the specifics of the peculiarities of floating point calculations, [watch this video by the Computerfile](https://www.youtube.com/watch?v=PZRI1IfStY0). //! //! Using **primitive** floating point number types in a blockchain context should also be avoided, as a //! single nondeterministic result could cause chaos for consensus along with the aforementioned issues. //! -//! //! The following methods represent different ways one can handle numbers safely natively in Rust, //! without fear of panic or unexpected behavior from wrapping. //! //! ### Checked Operations //! -//! **Checked operations** utilize a `Option` as a return type. This means that if the resulting -//! operation is invalid, i.e., an integer overflow, it will return `None`, and if successful, then -//! `Some`. The only downside of using this type is the need to handle the `Option` type accordingly. +//! **Checked operations** utilize a `Option` as a return type. This allows for simple pattern matching to catch any unexpected +//! behavior in the event of an overflow. //! //! This is an example of a valid operation: //! @@ -96,12 +86,12 @@ //! are a safe bet, as it presents two, predictable (and _erroring_) outcomes that can be handled //! accordingly (`Some` and `None`). //! -//! In a practical context, the resulting `Option` should be handled accordingly. The following -//! conventions can be seen from the within the Polkadot SDK, where an `Option` can be handled in one of +//! In a practical context, the resulting [`Option`] should be handled accordingly. The following +//! conventions can be seen from the within the Polkadot SDK, where it can be handled in one of //! two ways: //! -//! - As an `Option`, using the `if let` / `if` or `match` -//! - As a `Result`, via `ok_or` (or similar conversion to `Result` from `Option`) +//! - As an [`Option`], using the `if let` / `if` or `match` +//! - As a [`Result`], via `ok_or` (or similar conversion to [`Result`] from [`Option`]) //! //! #### Handling via Option - More Verbose //! @@ -122,7 +112,7 @@ //! //! #### Handling via Result - Less Verbose //! -//! In the Polkadot SDK codebase, you may see checked operations being handled as a `Result` via +//! In the Polkadot SDK codebase, you may see checked operations being handled as a [`Result`] via //! `ok_or`. This is a less verbose way of expressing the above. Which to use often boils down to //! the developer's preference: //! @@ -131,23 +121,6 @@ increase_balance_result )] //! -//! At a glance, this may seem confusing, as we just got done explaining how to handle a `Option`, not -//! `Result`. `Result` may be used as an alternative to `Option` where it ergonomically makes sense to -//! let the user know that something unexpected has happened. This is particularly useful in the context -//! of dispatchables within a Substrate pallet, for example, where information about a failure to -//! perform some action matters in the context of the application. -//! -//! #### Should you use `ok_or` or `ok_or_else`? -//! -//! You may see `ok_or` and `ok_or_else` being used interchangeably. In reality, they have the same -//! functionality with one caveat - `ok_or` is _eagerly_ evaluated, versus `ok_or_else` is _lazily_ -//! evaluated. Using `ok_or_else` is more performant, as if the `Option` is `Some()`, there is no need -//! to actually run the closure. `ok_or` is eager to make new allocations - regardless of whether it is -//! relevant or not, thereby making it slightly more expensive. -//! -//! [See more here.](https://rust-lang.github.io/rust-clippy/master/index.html#/or_fun_call) -//! -//! //! //! ### Saturated Operations //! @@ -166,7 +139,7 @@ //! //! As a recap, we covered the following concepts: //! -//! 1. **Checked** operations - using `Option` or `Result`, +//! 1. **Checked** operations - using [`Option`] or [`Result`], //! 2. **Saturated** operations - limited to the lower and upper bounds of a number type, //! 3. **Wrapped** operations (the default) - wrap around to above or below the bounds of a type, //! @@ -192,19 +165,19 @@ //! //!
//! Solution: Saturating or Checked -//! For Bob's balance problems, using a saturated_add or checked_add could've mitigated this issue. They simply would've reached the upper, or lower bounds, of the particular type for an on-chain balance. In other words: Bob's balance would've stayed at the maximum of the Balance type. +//! For Bob's balance problems, using a `saturated_add` or `checked_add` could've mitigated this issue. They simply would've reached the upper, or lower bounds, of the particular type for an on-chain balance. In other words: Bob's balance would've stayed at the maximum of the Balance type. //!
//! //! #### Alice's 'Underflowed' Balance //! //! Alice's balance has reached `0` after a transfer to Bob. Suddenly, she has been slashed on -//! EduChain, causing her balance to reach near the limit of `u32::MAX` - a very large amount - as +//! `EduChain`, causing her balance to reach near the limit of `u32::MAX` - a very large amount - as //! _wrapped operations_ can go both ways. Alice can now successfully vote using her new, //! overpowered token balance, destroying the integrity of the chain. //! //!
//! Solution: Saturating -//! For Alice's balance problem, using saturated_sub could've mitigated this issue. As debt or having a negative balance is not a concept within blockchains, a saturating calculation would've simply limited her balance to the lower bound of u32. +//! For Alice's balance problem, using `saturated_sub` could've mitigated this issue. As debt or having a negative balance is not a concept within blockchains, a saturating calculation would've simply limited her balance to the lower bound of u32. //! //! In other words: Alice's balance would've stayed at "0", even after being slashed. //! @@ -240,12 +213,11 @@ #![doc = simple_mermaid::mermaid!("../../../docs/mermaid/integer_operation_decision.mmd")] //! ## Fixed Point Arithmetic //! -//! The following code may use types from [`sp_arithmetic`]. Feel free to follow along by including -//! these crates. +//! The following code may use types from [`sp_arithmetic`]. //! //! Fixed point arithmetic solves the aforementioned problems of dealing with the uncertain //! nature of floating point numbers. Rather than use a radix point (`0.80`), a type which _represents_ -//! a floating point number in base 10 can be used instead. +//! a floating point number in base 10, i.e., a **fixed point number**, can be used instead. //! //! **Example - [`Perbill`](sp_arithmetic::Perbill), parts of a billion** //! @@ -297,7 +269,6 @@ //! point numbers in Substrate development. For this scenario, let's say we are demonstrating a _voting_ //! system which depends on reaching a certain threshold, or percentage, before it can be deemed valid. //! -//! //! For most cases, `Perbill` gives us a reasonable amount of precision for most applications, which is //! why we're using it here. @@ -337,6 +308,7 @@ mod tests { assert_eq!(add, None) } + #[docify::export] fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { // Get a user's current balance @@ -377,7 +349,8 @@ mod tests { #[docify::export] fn saturated_add_example() { - // Saturating add simply 'saturates' to the numeric bound of that type if it overflows. + // Saturating add simply 'saturates + // to the numeric bound of that type if it overflows. let add = u32::MAX.saturating_add(10); assert_eq!(add, u32::MAX) } @@ -390,7 +363,8 @@ mod tests { #[docify::export] fn perbill_example() { let p = Perbill::from_percent(80); - // 800000000 bil, or a representative of 0.800000000. Precision is in the billions place. + // 800000000 bil, or a representative of 0.800000000. + // Precision is in the billions place. assert_eq!(p.deconstruct(), 800000000); } diff --git a/docs/mermaid/integer_operation_decision.mmd b/docs/mermaid/integer_operation_decision.mmd index 8a73d5128bbd..243a856fb694 100644 --- a/docs/mermaid/integer_operation_decision.mmd +++ b/docs/mermaid/integer_operation_decision.mmd @@ -4,4 +4,4 @@ flowchart LR CH-->NEED["The user needs to know that the operation failed - and why"] CH-->DOUBT["Unsure whether this operation could fail/overflow"] ST-->SILENT["Silently reaching upper/lower bound will not result in any damage"] - ST-->REASON["In all reasonable circumstances, the number will not overflow"] + ST-->REASON["In all reasonable circumstances, the number will not overflow, but safety is still desired"] From dc50e7c758b24c3790086a41153517f6149f8e85 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 8 Nov 2023 09:25:16 -0500 Subject: [PATCH 04/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Oliver Tale-Yazdi --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 3114639b4797..41a0eeeaa8bb 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -26,7 +26,7 @@ //! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", naive_add)] //! -//! If we passed in overflow-able value sat runtime, this could actually panic (or wrap, if in release). +//! If we passed in overflow-able values at runtime, this could actually panic (or wrap, if in release). //! ```ignore //! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4. //! ``` From 538a10010789b64666ef22ff88e18056e164b3d5 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 8 Nov 2023 09:25:51 -0500 Subject: [PATCH 05/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Oliver Tale-Yazdi --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 41a0eeeaa8bb..de2acfa49a0e 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -57,7 +57,7 @@ //! deal with numbers and alternatives to floating point arithmetic. //! //! Rather they (should) use fixed-point arithmetic to mitigate the potential for inaccuracy, rounding errors, or other unexpected behavior. -//! For more on the specifics of the peculiarities of floating point calculations, [watch this video by the Computerfile](https://www.youtube.com/watch?v=PZRI1IfStY0). +//! For more on the specifics of the peculiarities of floating point calculations, [watch this video by the Computerphile](https://www.youtube.com/watch?v=PZRI1IfStY0). //! //! Using **primitive** floating point number types in a blockchain context should also be avoided, as a //! single nondeterministic result could cause chaos for consensus along with the aforementioned issues. From 330c245c48e400e455d15e757b8737a2ccf572b3 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 8 Nov 2023 09:25:58 -0500 Subject: [PATCH 06/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Oliver Tale-Yazdi --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index de2acfa49a0e..4bb9c5b6865c 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -67,7 +67,7 @@ //! //! ### Checked Operations //! -//! **Checked operations** utilize a `Option` as a return type. This allows for simple pattern matching to catch any unexpected +//! **Checked operations** utilize a `Option` as a return type. This allows for simple pattern matching to catch any unexpected //! behavior in the event of an overflow. //! //! This is an example of a valid operation: From cccd9a695d7cc8aa81109284697c0ae9f71efad5 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 8 Nov 2023 09:26:05 -0500 Subject: [PATCH 07/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Oliver Tale-Yazdi --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 4bb9c5b6865c..7ad6cfae5ed6 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -349,7 +349,7 @@ mod tests { #[docify::export] fn saturated_add_example() { - // Saturating add simply 'saturates + // Saturating add simply saturates // to the numeric bound of that type if it overflows. let add = u32::MAX.saturating_add(10); assert_eq!(add, u32::MAX) From 0c812453da10486cde4c8da47cac55e3563820a0 Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Wed, 8 Nov 2023 09:31:27 -0500 Subject: [PATCH 08/29] fmt --- .../src/polkadot_sdk/frame_runtime.rs | 9 +- developer-hub/src/polkadot_sdk/substrate.rs | 1 - .../src/reference_docs/extrinsic_encoding.rs | 177 ++++----- .../reference_docs/frame_system_accounts.rs | 1 - .../safe_defensive_programming.rs | 361 +++++++++--------- 5 files changed, 270 insertions(+), 279 deletions(-) diff --git a/developer-hub/src/polkadot_sdk/frame_runtime.rs b/developer-hub/src/polkadot_sdk/frame_runtime.rs index 56f1e175bf8f..4b6710e0b385 100644 --- a/developer-hub/src/polkadot_sdk/frame_runtime.rs +++ b/developer-hub/src/polkadot_sdk/frame_runtime.rs @@ -15,7 +15,8 @@ //! //! ## Introduction //! -//! As described in [`crate::reference_docs::wasm_meta_protocol`], at a high-level substrate-based blockchains are composed of two parts: +//! As described in [`crate::reference_docs::wasm_meta_protocol`], at a high-level substrate-based +//! blockchains are composed of two parts: //! //! 1. A *runtime* which represents the state transition function (i.e. "Business Logic") of a //! blockchain, and is encoded as a Wasm blob. @@ -79,10 +80,8 @@ //! a valid Runtime form the point of view of a Substrate cliemt (see //! [`crate::reference_docs::wasm_meta_protocol`]). Notable examples are: //! -//! * writing a runtime in pure Rust, as done in [this -//! template](https://github.com/JoshOrndorff/frameless-node-template). -//! * writing a runtime in AssemblyScript,as explored in [this -//! project](https://github.com/LimeChain/subsembly). +//! * writing a runtime in pure Rust, as done in [this template](https://github.com/JoshOrndorff/frameless-node-template). +//! * writing a runtime in AssemblyScript,as explored in [this project](https://github.com/LimeChain/subsembly). #[cfg(test)] mod tests { diff --git a/developer-hub/src/polkadot_sdk/substrate.rs b/developer-hub/src/polkadot_sdk/substrate.rs index 2e3471853966..83a19fa8f1a8 100644 --- a/developer-hub/src/polkadot_sdk/substrate.rs +++ b/developer-hub/src/polkadot_sdk/substrate.rs @@ -51,7 +51,6 @@ //! //! > A notable Substrate-based blockchain that has built both custom FRAME pallets and custom //! > client-side components is . -//! #![doc = simple_mermaid::mermaid!("../../../docs/mermaid/substrate_dev.mmd")] //! //! ## Structure diff --git a/developer-hub/src/reference_docs/extrinsic_encoding.rs b/developer-hub/src/reference_docs/extrinsic_encoding.rs index 7c76c400608f..b10b100bfbbe 100644 --- a/developer-hub/src/reference_docs/extrinsic_encoding.rs +++ b/developer-hub/src/reference_docs/extrinsic_encoding.rs @@ -1,12 +1,13 @@ //! # Constructing and Signing Extrinsics //! //! Extrinsics are payloads that are stored in blocks which are responsible for altering the state -//! of a blockchain via the [_state transition function_][crate::reference_docs::blockchain_state_machines]. +//! of a blockchain via the [_state transition +//! function_][crate::reference_docs::blockchain_state_machines]. //! //! Substrate is configurable enough that extrinsics can take any format. In practice, runtimes //! tend to use our [`sp_runtime::generic::UncheckedExtrinsic`] type to represent extrinsics, -//! because it's generic enough to cater for most (if not all) use cases. In Polkadot, this is configured -//! [here](https://github.com/polkadot-fellows/runtimes/blob/94b2798b69ba6779764e20a50f056e48db78ebef/relay/polkadot/src/lib.rs#L1478) +//! because it's generic enough to cater for most (if not all) use cases. In Polkadot, this is +//! configured [here](https://github.com/polkadot-fellows/runtimes/blob/94b2798b69ba6779764e20a50f056e48db78ebef/relay/polkadot/src/lib.rs#L1478) //! at the time of writing. //! //! What follows is a description of how extrinsics based on this @@ -29,7 +30,6 @@ //! ``` //! //! For clarity, the actual implementation in Substrate looks like this: -//! #![doc = docify::embed!("../substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs", unchecked_extrinsic_encode_impl)] //! //! Let's look at how each of these details is constructed: @@ -102,15 +102,16 @@ //! //! - The actual SCALE encoding of the signed extension type itself; this is what will form our //! `signed_extensions_extra` bytes. -//! - An `AdditionalSigned` type. This is SCALE encoded into the `signed_extensions_additional` -//! data of the _signed payload_ (see below). +//! - An `AdditionalSigned` type. This is SCALE encoded into the `signed_extensions_additional` data +//! of the _signed payload_ (see below). //! //! Either (or both) of these can encode to zero bytes. //! //! Each chain configures the set of signed extensions that it uses in its runtime configuration. //! At the time of writing, Polkadot configures them //! [here](https://github.com/polkadot-fellows/runtimes/blob/1dc04eb954eadf8aadb5d83990b89662dbb5a074/relay/polkadot/src/lib.rs#L1432C25-L1432C25). -//! Some of the common signed extensions are defined [here][frame::deps::frame_system#signed-extensions]. +//! Some of the common signed extensions are defined +//! [here][frame::deps::frame_system#signed-extensions]. //! //! Information about exactly which signed extensions are present on a chain and in what order is //! also a part of the metadata for the chain. For V15 metadata, it can be @@ -119,15 +120,16 @@ //! ## call_data //! //! This is the main payload of the extrinsic, which is used to determine how the chain's state is -//! altered. This is defined by the second generic parameter of [`sp_runtime::generic::UncheckedExtrinsic`]. +//! altered. This is defined by the second generic parameter of +//! [`sp_runtime::generic::UncheckedExtrinsic`]. //! //! A call can be anything that implements [`Encode`][frame::deps::codec::Encode]. In FRAME-based //! runtimes, a call is represented as an enum of enums, where the outer enum represents the FRAME //! pallet being called, and the inner enum represents the call being made within that pallet, and -//! any arguments to it. Read more about the call enum [here][crate::reference_docs::frame_composite_enums]. +//! any arguments to it. Read more about the call enum +//! [here][crate::reference_docs::frame_composite_enums]. //! //! FRAME `Call` enums are automatically generated, and end up looking something like this: -//! #![doc = docify::embed!("./src/reference_docs/extrinsic_encoding.rs", call_data)] //! //! In pseudo-code, this `Call` enum encodes equivalently to: @@ -140,12 +142,12 @@ //! ) //! ``` //! -//! - `pallet_index` is a single byte denoting the index of the pallet that we are calling into, -//! and is what the tag of the outermost enum will encode to. +//! - `pallet_index` is a single byte denoting the index of the pallet that we are calling into, and +//! is what the tag of the outermost enum will encode to. //! - `call_index` is a single byte denoting the index of the call that we are making the pallet, //! and is what the tag of the inner enum will encode to. -//! - `call_args` are the SCALE encoded bytes for each of the arguments that the call expects, -//! and are typically provided as values to the inner enum. +//! - `call_args` are the SCALE encoded bytes for each of the arguments that the call expects, and +//! are typically provided as values to the inner enum. //! //! Information about the pallets that exist for a chain (including their indexes), the calls //! available in each pallet (including their indexes), and the arguments required for each call @@ -185,104 +187,91 @@ //! //! Using [`sp_runtime::generic::UncheckedExtrinsic`], we can construct and encode an extrinsic //! as follows: -//! #![doc = docify::embed!("./src/reference_docs/extrinsic_encoding.rs", encoding_example)] #[docify::export] pub mod call_data { - use parity_scale_codec::{Encode, Decode}; + use parity_scale_codec::{Decode, Encode}; - // The outer enum composes calls within - // different pallets together. We have two - // pallets, "PalletA" and "PalletB". - #[derive(Encode, Decode)] - pub enum Call { - #[codec(index = 0)] - PalletA(PalletACall), - #[codec(index = 7)] - PalletB(PalletBCall) - } + // The outer enum composes calls within + // different pallets together. We have two + // pallets, "PalletA" and "PalletB". + #[derive(Encode, Decode)] + pub enum Call { + #[codec(index = 0)] + PalletA(PalletACall), + #[codec(index = 7)] + PalletB(PalletBCall), + } - // An inner enum represents the calls within - // a specific pallet. "PalletA" has one call, - // "Foo". - #[derive(Encode, Decode)] - pub enum PalletACall { - #[codec(index = 0)] - Foo(String) - } + // An inner enum represents the calls within + // a specific pallet. "PalletA" has one call, + // "Foo". + #[derive(Encode, Decode)] + pub enum PalletACall { + #[codec(index = 0)] + Foo(String), + } - #[derive(Encode, Decode)] - pub enum PalletBCall { - #[codec(index = 0)] - Bar(String) - } + #[derive(Encode, Decode)] + pub enum PalletBCall { + #[codec(index = 0)] + Bar(String), + } } #[docify::export] pub mod encoding_example { - use crate::reference_docs::signed_extensions::signed_extensions_example; - use super::call_data::{ Call, PalletACall }; - use sp_runtime::{MultiAddress, MultiSignature}; - use sp_runtime::generic::{UncheckedExtrinsic, SignedPayload}; - use sp_core::crypto::AccountId32; - use sp_keyring::sr25519::Keyring; - use parity_scale_codec::Encode; + use super::call_data::{Call, PalletACall}; + use crate::reference_docs::signed_extensions::signed_extensions_example; + use parity_scale_codec::Encode; + use sp_core::crypto::AccountId32; + use sp_keyring::sr25519::Keyring; + use sp_runtime::{ + generic::{SignedPayload, UncheckedExtrinsic}, + MultiAddress, MultiSignature, + }; - // Define some signed extensions to use. We'll use a couple of examples - // from the signed extensions reference doc. - type SignedExtensions = ( - signed_extensions_example::AddToPayload, - signed_extensions_example::AddToSignaturePayload, - ); + // Define some signed extensions to use. We'll use a couple of examples + // from the signed extensions reference doc. + type SignedExtensions = + (signed_extensions_example::AddToPayload, signed_extensions_example::AddToSignaturePayload); - // We'll use `UncheckedExtrinsic` to encode our extrinsic for us. We set - // the address and signature type to those used on Polkadot, use our custom - // `Call` type, and use our custom set of `SignedExtensions`. - type Extrinsic = UncheckedExtrinsic< - MultiAddress, - Call, - MultiSignature, - SignedExtensions - >; + // We'll use `UncheckedExtrinsic` to encode our extrinsic for us. We set + // the address and signature type to those used on Polkadot, use our custom + // `Call` type, and use our custom set of `SignedExtensions`. + type Extrinsic = + UncheckedExtrinsic, Call, MultiSignature, SignedExtensions>; - pub fn encode_demo_extrinsic() -> Vec { - // The "from" address will be our Alice dev account. - let from_address = MultiAddress::::Id(Keyring::Alice.to_account_id()); + pub fn encode_demo_extrinsic() -> Vec { + // The "from" address will be our Alice dev account. + let from_address = MultiAddress::::Id(Keyring::Alice.to_account_id()); - // We provide some values for our expected signed extensions. - let signed_extensions = ( - signed_extensions_example::AddToPayload(1), - signed_extensions_example::AddToSignaturePayload - ); + // We provide some values for our expected signed extensions. + let signed_extensions = ( + signed_extensions_example::AddToPayload(1), + signed_extensions_example::AddToSignaturePayload, + ); - // Construct our call data: - let call_data = Call::PalletA(PalletACall::Foo("Hello".to_string())); + // Construct our call data: + let call_data = Call::PalletA(PalletACall::Foo("Hello".to_string())); - // The signed payload. This takes care of encoding the call_data, - // signed_extensions_extra and signed_extensions_additional, and hashing - // the result if it's > 256 bytes: - let signed_payload = SignedPayload::new( - &call_data, - signed_extensions.clone(), - ); + // The signed payload. This takes care of encoding the call_data, + // signed_extensions_extra and signed_extensions_additional, and hashing + // the result if it's > 256 bytes: + let signed_payload = SignedPayload::new(&call_data, signed_extensions.clone()); - // Sign the signed payload with our Alice dev account's private key, - // and wrap the signature into the expected type: - let signature = { - let sig = Keyring::Alice.sign(&signed_payload.encode()); - MultiSignature::Sr25519(sig) - }; + // Sign the signed payload with our Alice dev account's private key, + // and wrap the signature into the expected type: + let signature = { + let sig = Keyring::Alice.sign(&signed_payload.encode()); + MultiSignature::Sr25519(sig) + }; - // Now, we can build and encode our extrinsic: - let ext = Extrinsic::new_signed( - call_data, - from_address, - signature, - signed_extensions - ); + // Now, we can build and encode our extrinsic: + let ext = Extrinsic::new_signed(call_data, from_address, signature, signed_extensions); - let encoded_ext = ext.encode(); - encoded_ext - } + let encoded_ext = ext.encode(); + encoded_ext + } } diff --git a/developer-hub/src/reference_docs/frame_system_accounts.rs b/developer-hub/src/reference_docs/frame_system_accounts.rs index 5f2dd91318e6..ae9d2c9e0cb3 100644 --- a/developer-hub/src/reference_docs/frame_system_accounts.rs +++ b/developer-hub/src/reference_docs/frame_system_accounts.rs @@ -1,7 +1,6 @@ //! # FRAME Accounts //! //! How `frame_system` handles accountIds. Nonce. Consumers and Providers, reference counting. -//! // - poorly understood topics, needs one great article to rul them all. // - https://github.com/paritytech/substrate/issues/14425 diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 7ad6cfae5ed6..2909a5df84d3 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -1,10 +1,11 @@ -//! As our runtime should _never_ panic; this includes eliminating the possibility of integer overflows, -//! converting between number types, or even handling floating point usage with fixed point arithmetic -//! to mitigate issues that come with floating point calculations. +//! As our runtime should _never_ panic; this includes eliminating the possibility of integer +//! overflows, converting between number types, or even handling floating point usage with fixed +//! point arithmetic to mitigate issues that come with floating point calculations. //! //! ## Integer Overflow //! -//! The Rust compiler prevents any sort of static overflow from happening at compile time, for example: +//! The Rust compiler prevents any sort of static overflow from happening at compile time, for +//! example: //! //! ```ignore //! let overflow = u8::MAX + 10; @@ -20,71 +21,69 @@ //! ``` //! //! -//! However in the runtime context, we don't always have control over what is being supplied as a parameter. For -//! example, even this simple adding function could present one of two outcomes depending on whether it is in -//! **release** or **debug** mode: -//! +//! However in the runtime context, we don't always have control over what is being supplied as a +//! parameter. For example, even this simple adding function could present one of two outcomes +//! depending on whether it is in **release** or **debug** mode: #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", naive_add)] //! -//! If we passed in overflow-able values at runtime, this could actually panic (or wrap, if in release). -//! ```ignore +//! If we passed in overflow-able values at runtime, this could actually panic (or wrap, if in +//! release). ```ignore //! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4. //! ``` //! -//! The Rust compiler would panic in **debug** mode in the event of an integer overflow. In **release** -//! mode, it resorts to silently _wrapping_ the overflowed amount in a modular fashion, (hence returning -//! `4`). +//! The Rust compiler would panic in **debug** mode in the event of an integer overflow. In +//! **release** mode, it resorts to silently _wrapping_ the overflowed amount in a modular fashion, +//! (hence returning `4`). //! -//! It is actually the _silent_ portion of this behavior that presents a real issue - as it may be an -//! unintended, but also a very _silent killer_ in terms of producing bugs. In fact, it would have been -//! better for this type of behavior to produce some sort of error, or even `panic!`, as in that -//! scenario, at least such behavior could become obvious. Especially in the context of blockchain -//! development, where unsafe arithmetic could produce unexpected consequences. +//! It is actually the _silent_ portion of this behavior that presents a real issue - as it may be +//! an unintended, but also a very _silent killer_ in terms of producing bugs. In fact, it would +//! have been better for this type of behavior to produce some sort of error, or even `panic!`, as +//! in that scenario, at least such behavior could become obvious. Especially in the context of +//! blockchain development, where unsafe arithmetic could produce unexpected consequences. //! -//! A quick example is a user's balance overflowing: the default behavior of wrapping could result in -//! the user's balance starting from zero, or vice versa, of a `0` balance turning into the `MAX` of -//! some type. Naturally, this could lead to various exploits and issues down the road, which if failing -//! silently, would be difficult to trace and rectify. +//! A quick example is a user's balance overflowing: the default behavior of wrapping could result +//! in the user's balance starting from zero, or vice versa, of a `0` balance turning into the `MAX` +//! of some type. Naturally, this could lead to various exploits and issues down the road, which if +//! failing silently, would be difficult to trace and rectify. //! -//! Luckily, there are ways to both represent and handle these scenarios depending on our specific use -//! case natively built into Rust, as well as libraries like [`sp_arithmetic`]. +//! Luckily, there are ways to both represent and handle these scenarios depending on our specific +//! use case natively built into Rust, as well as libraries like [`sp_arithmetic`]. //! //! ## Infallible Arithmetic //! -//! Our main objective in runtime development is to reduce the likelihood of any *unintended* or *undefined* behavior. -//! Intentional and predictable design should be our first and foremost property for -//! ensuring a well running, safely designed system. Both Rust and Substrate both provide safe ways to -//! deal with numbers and alternatives to floating point arithmetic. +//! Our main objective in runtime development is to reduce the likelihood of any *unintended* or +//! *undefined* behavior. Intentional and predictable design should be our first and foremost +//! property for ensuring a well running, safely designed system. Both Rust and Substrate both +//! provide safe ways to deal with numbers and alternatives to floating point arithmetic. //! -//! Rather they (should) use fixed-point arithmetic to mitigate the potential for inaccuracy, rounding errors, or other unexpected behavior. -//! For more on the specifics of the peculiarities of floating point calculations, [watch this video by the Computerphile](https://www.youtube.com/watch?v=PZRI1IfStY0). +//! Rather they (should) use fixed-point arithmetic to mitigate the potential for inaccuracy, +//! rounding errors, or other unexpected behavior. For more on the specifics of the peculiarities of floating point calculations, [watch this video by the Computerphile](https://www.youtube.com/watch?v=PZRI1IfStY0). //! -//! Using **primitive** floating point number types in a blockchain context should also be avoided, as a -//! single nondeterministic result could cause chaos for consensus along with the aforementioned issues. +//! Using **primitive** floating point number types in a blockchain context should also be avoided, +//! as a single nondeterministic result could cause chaos for consensus along with the +//! aforementioned issues. //! //! The following methods represent different ways one can handle numbers safely natively in Rust, //! without fear of panic or unexpected behavior from wrapping. //! //! ### Checked Operations //! -//! **Checked operations** utilize a `Option` as a return type. This allows for simple pattern matching to catch any unexpected -//! behavior in the event of an overflow. +//! **Checked operations** utilize a `Option` as a return type. This allows for simple pattern +//! matching to catch any unexpected behavior in the event of an overflow. //! //! This is an example of a valid operation: -//! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", checked_add_example)] //! -//! This is an example of an invalid operation, in this case, a simulated integer overflow, which would -//! simply result in `None`: -//! +//! This is an example of an invalid operation, in this case, a simulated integer overflow, which +//! would simply result in `None`: #![doc = docify::embed!( "./src/reference_docs/safe_defensive_programming.rs", checked_add_handle_error_example )] //! -//! Typically, if you aren't sure about which operation to use for runtime math, **checked** operations -//! are a safe bet, as it presents two, predictable (and _erroring_) outcomes that can be handled -//! accordingly (`Some` and `None`). +//! Typically, if you aren't sure about which operation to use for runtime math, **checked** +//! operations are a safe bet, as it presents two, predictable (and _erroring_) outcomes that can be +//! handled accordingly (`Some` and `None`). //! //! In a practical context, the resulting [`Option`] should be handled accordingly. The following //! conventions can be seen from the within the Polkadot SDK, where it can be handled in one of @@ -97,11 +96,9 @@ //! //! Because wrapped operations return `Option`, you can use a more verbose/explicit form of error //! handling via `if` or `if let`: -//! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", increase_balance)] //! //! Optionally, `match` may also be directly used in a more concise manner: -//! #![doc = docify::embed!( "./src/reference_docs/safe_defensive_programming.rs", increase_balance_match @@ -115,7 +112,6 @@ //! In the Polkadot SDK codebase, you may see checked operations being handled as a [`Result`] via //! `ok_or`. This is a less verbose way of expressing the above. Which to use often boils down to //! the developer's preference: -//! #![doc = docify::embed!( "./src/reference_docs/safe_defensive_programming.rs", increase_balance_result @@ -126,14 +122,13 @@ //! //! Saturating a number limits it to the type's upper or lower bound, no matter the integer would //! overflow in runtime. For example, adding to `u32::MAX` would simply limit itself to `u32::MAX`: -//! #![doc = docify::embed!( "./src/reference_docs/safe_defensive_programming.rs", saturated_add_example )] //! -//! Saturating calculations can be used if one is very sure that something won't overflow, but wants to -//! avoid introducing the notion of any potential-panic or wrapping behavior. +//! Saturating calculations can be used if one is very sure that something won't overflow, but wants +//! to avoid introducing the notion of any potential-panic or wrapping behavior. //! //! ### Mathematical Operations in Substrate Development - Further Context //! @@ -145,28 +140,30 @@ //! //! #### The problem with 'default' wrapped operations //! -//! **Wrapped operations** cause the overflow to wrap around to either the maximum or minimum of that -//! type. Imagine this in the context of a blockchain, where there are balances, voting counters, nonces -//! for transactions, and other aspects of a blockchain. +//! **Wrapped operations** cause the overflow to wrap around to either the maximum or minimum of +//! that type. Imagine this in the context of a blockchain, where there are balances, voting +//! counters, nonces for transactions, and other aspects of a blockchain. //! -//! Some of these mechanisms can be more critical than others. It's for this reason that we may consider -//! some other ways of dealing with runtime arithmetic, such as saturated or checked operations, that -//! won't carry these potential consequences. +//! Some of these mechanisms can be more critical than others. It's for this reason that we may +//! consider some other ways of dealing with runtime arithmetic, such as saturated or checked +//! operations, that won't carry these potential consequences. //! //! -//! While it may seem trivial, choosing how to handle numbers is quite important. As a thought exercise, -//! here are some scenarios of which will shed more light on when to use which. +//! While it may seem trivial, choosing how to handle numbers is quite important. As a thought +//! exercise, here are some scenarios of which will shed more light on when to use which. //! //! #### Bob's Overflowed Balance //! -//! **Bob's** balance exceeds the `Balance` type on the `EduChain`. Because the pallet developer did not -//! handle the calculation to add to Bob's balance with any regard to this overflow, **Bob's** balance -//! is now essentially `0`, the operation **wrapped**. +//! **Bob's** balance exceeds the `Balance` type on the `EduChain`. Because the pallet developer did +//! not handle the calculation to add to Bob's balance with any regard to this overflow, **Bob's** +//! balance is now essentially `0`, the operation **wrapped**. //! //!
//! Solution: Saturating or Checked -//! For Bob's balance problems, using a `saturated_add` or `checked_add` could've mitigated this issue. They simply would've reached the upper, or lower bounds, of the particular type for an on-chain balance. In other words: Bob's balance would've stayed at the maximum of the Balance type. -//!
+//! For Bob's balance problems, using a `saturated_add` or `checked_add` could've mitigated this +//! issue. They simply would've reached the upper, or lower bounds, of the particular type for an +//! on-chain balance. In other words: Bob's balance would've stayed at the maximum of the Balance +//! type.
//! //! #### Alice's 'Underflowed' Balance //! @@ -177,24 +174,30 @@ //! //!
//! Solution: Saturating -//! For Alice's balance problem, using `saturated_sub` could've mitigated this issue. As debt or having a negative balance is not a concept within blockchains, a saturating calculation would've simply limited her balance to the lower bound of u32. +//! For Alice's balance problem, using `saturated_sub` could've mitigated this issue. As debt or +//! having a negative balance is not a concept within blockchains, a saturating calculation would've +//! simply limited her balance to the lower bound of u32. //! //! In other words: Alice's balance would've stayed at "0", even after being slashed. //! -//! This is also an example that while one system may work in isolation, shared interfaces, such as the notion of balances, are often shared across multiple pallets - meaning these small changes can make a big difference in outcome. -//!
+//! This is also an example that while one system may work in isolation, shared interfaces, such +//! as the notion of balances, are often shared across multiple pallets - meaning these small +//! changes can make a big difference in outcome. //! //! #### Proposals' ID Overwrite //! //! The type for counting the number of proposals on-chain is represented by a `u8` number, called -//! `proposals_count`. Every time a new proposal is added to the system, this number increases. With the -//! proposal pallet being high in usage, it has reached `u8::MAX`'s limit of `255`, causing -//! `proposals_count` to go to `0`. Unfortunately, this resulted in new proposals overwriting old ones, -//! effectively erasing any notion of past proposals! +//! `proposals_count`. Every time a new proposal is added to the system, this number increases. With +//! the proposal pallet being high in usage, it has reached `u8::MAX`'s limit of `255`, causing +//! `proposals_count` to go to `0`. Unfortunately, this resulted in new proposals overwriting old +//! ones, effectively erasing any notion of past proposals! //! //!
//! Solution: Checked -//! For the proposal IDs, proper handling via `checked` math would've been much more suitable, Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to ensure that the next proposal ID would've been valid would've been a viable way to let the user know the state of their proposal: +//! For the proposal IDs, proper handling via `checked` math would've been much more suitable, +//! Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to +//! ensure that the next proposal ID would've been valid would've been a viable way to let the user +//! know the state of their proposal: //! //! ```ignore //! let next_proposal_id = current_count.checked_add(1).ok_or_else(|| Error::TooManyProposals)?; @@ -203,39 +206,41 @@ //!
//! //! -//! From the above, we can clearly see the problematic nature of seemingly simple operations in runtime. -//! Of course, it may be that using checked math is perfectly fine under some scenarios - such as -//! certain balance being never realistically attainable, or a number type being so large that it could -//! never realistically overflow unless one sent thousands of transactions to the network. +//! From the above, we can clearly see the problematic nature of seemingly simple operations in +//! runtime. Of course, it may be that using checked math is perfectly fine under some scenarios - +//! such as certain balance being never realistically attainable, or a number type being so large +//! that it could never realistically overflow unless one sent thousands of transactions to the +//! network. //! //! ### Decision Chart: When to use which? -//! #![doc = simple_mermaid::mermaid!("../../../docs/mermaid/integer_operation_decision.mmd")] //! ## Fixed Point Arithmetic //! //! The following code may use types from [`sp_arithmetic`]. //! //! Fixed point arithmetic solves the aforementioned problems of dealing with the uncertain -//! nature of floating point numbers. Rather than use a radix point (`0.80`), a type which _represents_ -//! a floating point number in base 10, i.e., a **fixed point number**, can be used instead. +//! nature of floating point numbers. Rather than use a radix point (`0.80`), a type which +//! _represents_ a floating point number in base 10, i.e., a **fixed point number**, can be used +//! instead. //! //! **Example - [`Perbill`](sp_arithmetic::Perbill), parts of a billion** -//! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", perbill_example)] //! //! **Example - [`Percent`](sp_arithmetic::Percent), parts of a hundred** -//! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_example)] //! //! Note that `190 / 400 = 0.475`, and that `Percent` represents it as a _rounded down_, fixed point -//! number (`47`). Unlike primitive types, types that implement `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. +//! number (`47`). Unlike primitive types, types that implement `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. //! //! ### Using 'PerThing' In Practice //! -//! [`sp_arithmetic`] contains a trait called [`PerThing`](sp_arithmetic::PerThing), allowing a custom type to be implemented specifically for fixed point arithmetic. While a number of fixed-point types are introduced, let's focus on a few specific examples that implement `PerThing`: +//! [`sp_arithmetic`] contains a trait called [`PerThing`](sp_arithmetic::PerThing), allowing a +//! custom type to be implemented specifically for fixed point arithmetic. While a number of +//! fixed-point types are introduced, let's focus on a few specific examples that implement +//! `PerThing`: //! //! - [`Percent`](sp_arithmetic::Percent) - parts of one hundred. //! - [`Permill`](sp_arithmetic::Permill) - parts of a million. @@ -254,7 +259,6 @@ //! //! As stated, one can also perform mathematics using these types directly. For example, finding the //! percentage of a particular item: -//! #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_mult)] //! //! With the knowledge of how these types operate in relation to other numbers, let's explore how @@ -262,115 +266,116 @@ //! //! ### Fixed Point Math in Substrate Development - Further Context //! -//! You will find types like [`Perbill`](sp_arithmetic::Perbill) being used often in pallet development. [`pallet_referenda`] is a good example of a pallet -//! which makes good use of fixed point arithmetic to calculate +//! You will find types like [`Perbill`](sp_arithmetic::Perbill) being used often in pallet +//! development. [`pallet_referenda`] is a good example of a pallet which makes good use of fixed +//! point arithmetic to calculate //! //! Let's examine the usage of `Perbill` and how exactly we can use it as an alternative to floating -//! point numbers in Substrate development. For this scenario, let's say we are demonstrating a _voting_ -//! system which depends on reaching a certain threshold, or percentage, before it can be deemed valid. -//! -//! For most cases, `Perbill` gives us a reasonable amount of precision for most applications, which is -//! why we're using it here. +//! point numbers in Substrate development. For this scenario, let's say we are demonstrating a +//! _voting_ system which depends on reaching a certain threshold, or percentage, before it can be +//! deemed valid. +//! +//! For most cases, `Perbill` gives us a reasonable amount of precision for most applications, which +//! is why we're using it here. #[cfg(test)] mod tests { - enum BlockchainError { - Overflow, - } - - type Address = &'static str; + enum BlockchainError { + Overflow, + } - struct Runtime; + type Address = &'static str; - impl Runtime { - fn get_balance(account: Address) -> u64 { - 0 - } - } + struct Runtime; - #[docify::export] - fn naive_add(x: u8, y: u8) -> u8 { - x + y - } + impl Runtime { + fn get_balance(account: Address) -> u64 { + 0 + } + } - #[docify::export] - fn checked_add_example() { - // This is valid, as 20 is perfectly within the bounds of u32. - let add = (10u32).checked_add(10); - assert_eq!(add, Some(20)) - } + #[docify::export] + fn naive_add(x: u8, y: u8) -> u8 { + x + y + } - #[docify::export] - fn checked_add_handle_error_example() { - // This is invalid - we are adding something to the max of u32::MAX, which would overflow. - // Luckily, checked_add just marks this as None! - let add = u32::MAX.checked_add(10); - assert_eq!(add, None) - } + #[docify::export] + fn checked_add_example() { + // This is valid, as 20 is perfectly within the bounds of u32. + let add = (10u32).checked_add(10); + assert_eq!(add, Some(20)) + } + #[docify::export] + fn checked_add_handle_error_example() { + // This is invalid - we are adding something to the max of u32::MAX, which would overflow. + // Luckily, checked_add just marks this as None! + let add = u32::MAX.checked_add(10); + assert_eq!(add, None) + } - #[docify::export] - fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - if let Some(new_balance) = balance.checked_add(amount) { - Runtime::set_balance(account, new_balance); - return Ok(()); - } else { - return Err(BlockchainError::Overflow); - } - } + #[docify::export] + fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + if let Some(new_balance) = balance.checked_add(amount) { + Runtime::set_balance(account, new_balance); + return Ok(()); + } else { + return Err(BlockchainError::Overflow); + } + } - #[docify::export] - fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - let new_balance = match balance.checked_add(amount) { - Some(balance) => balance, - None => { - return Err(BlockchainError::Overflow); - } - }; - Runtime::set_balance(account, new_balance); - Ok(()) - } + #[docify::export] + fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + let new_balance = match balance.checked_add(amount) { + Some(balance) => balance, + None => { + return Err(BlockchainError::Overflow); + }, + }; + Runtime::set_balance(account, new_balance); + Ok(()) + } - #[docify::export] - fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - this time, by using `ok_or` - let new_balance = balance.checked_add(amount).ok_or_else(|| BlockchainError::Overflow)?; - Runtime::set_balance(account, new_balance); - Ok(()) - } + #[docify::export] + fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount - this time, by using `ok_or` + let new_balance = balance.checked_add(amount).ok_or_else(|| BlockchainError::Overflow)?; + Runtime::set_balance(account, new_balance); + Ok(()) + } - #[docify::export] - fn saturated_add_example() { - // Saturating add simply saturates - // to the numeric bound of that type if it overflows. - let add = u32::MAX.saturating_add(10); - assert_eq!(add, u32::MAX) - } - #[docify::export] - 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] - 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] + fn saturated_add_example() { + // Saturating add simply saturates + // to the numeric bound of that type if it overflows. + let add = u32::MAX.saturating_add(10); + assert_eq!(add, u32::MAX) + } + #[docify::export] + 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] + 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] - fn percent_example() { - let percent = Percent::from_rational(190u32, 400u32); - assert_eq!(percent.deconstruct(), 47); - } + #[docify::export] + fn percent_example() { + let percent = Percent::from_rational(190u32, 400u32); + assert_eq!(percent.deconstruct(), 47); + } } From 7a1e59f5238d10c82bf4e971b45549fa922c9b72 Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Wed, 8 Nov 2023 16:35:43 -0500 Subject: [PATCH 09/29] start addressing comments --- .../safe_defensive_programming.rs | 278 +++++++++++------- 1 file changed, 170 insertions(+), 108 deletions(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 2909a5df84d3..4204d7ebf24a 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -27,7 +27,8 @@ #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", naive_add)] //! //! If we passed in overflow-able values at runtime, this could actually panic (or wrap, if in -//! release). ```ignore +//! release). +//! ```ignore //! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4. //! ``` //! @@ -159,7 +160,7 @@ //! balance is now essentially `0`, the operation **wrapped**. //! //!
-//! Solution: Saturating or Checked +//! Solution: Saturating or Checked //! For Bob's balance problems, using a `saturated_add` or `checked_add` could've mitigated this //! issue. They simply would've reached the upper, or lower bounds, of the particular type for an //! on-chain balance. In other words: Bob's balance would've stayed at the maximum of the Balance @@ -193,7 +194,7 @@ //! ones, effectively erasing any notion of past proposals! //! //!
-//! Solution: Checked +//! Solution: Checked //! For the proposal IDs, proper handling via `checked` math would've been much more suitable, //! Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to //! ensure that the next proposal ID would've been valid would've been a viable way to let the user @@ -216,31 +217,43 @@ #![doc = simple_mermaid::mermaid!("../../../docs/mermaid/integer_operation_decision.mmd")] //! ## Fixed Point Arithmetic //! -//! The following code may use types from [`sp_arithmetic`]. +//! The following code uses types from [`sp_arithmetic`]. //! //! Fixed point arithmetic solves the aforementioned problems of dealing with the uncertain //! nature of floating point numbers. Rather than use a radix point (`0.80`), a type which //! _represents_ a floating point number in base 10, i.e., a **fixed point number**, can be used //! instead. //! -//! **Example - [`Perbill`](sp_arithmetic::Perbill), parts of a billion** +//! For use cases which operate within the range of `[0, 1]` types which implement [`PerThing`](sp_arithmetic::PerThing) are used: +//! - **[`Perbill`](sp_arithmetic::Perbill), parts of a billion** #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", perbill_example)] //! -//! **Example - [`Percent`](sp_arithmetic::Percent), parts of a hundred** +//! - **[`Percent`](sp_arithmetic::Percent), parts of a hundred** #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_example)] //! //! Note that `190 / 400 = 0.475`, and that `Percent` represents it as a _rounded down_, fixed point -//! number (`47`). Unlike primitive types, types that implement `PerThing` will also not overflow, +//! number (`47`). Unlike primitive types, types that implement [`PerThing`](sp_arithmetic::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. //! -//! ### Using 'PerThing' In Practice +//! For use cases which require precision beyond the range of `[0, 1]`, there are a number of other fixed-point types to use: +//! +//! - [`FixedU64`](sp_arithmetic::FixedU64) and [`FixedI64`](sp_arithmetic::FixedI64) +//! - [`FixedI128`](sp_arithmetic::FixedU128) and [`FixedU128`](sp_arithmetic::FixedI128) +//! +//! Similar to types that implement [`PerThing`](sp_arithmetic::PerThing), these are also fixed-point types, however, they are able to represent larger numbers: +//! +#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", fixed_u64)] +//! +//! Let's now explore these types in practice, and how they may be used with pallets to perform safer calculations in the runtime. +//! +//! ### 'PerThing' In Practice //! //! [`sp_arithmetic`] contains a trait called [`PerThing`](sp_arithmetic::PerThing), allowing a //! custom type to be implemented specifically for fixed point arithmetic. While a number of //! fixed-point types are introduced, let's focus on a few specific examples that implement -//! `PerThing`: +//! [`PerThing`](sp_arithmetic::PerThing): //! //! - [`Percent`](sp_arithmetic::Percent) - parts of one hundred. //! - [`Permill`](sp_arithmetic::Permill) - parts of a million. @@ -254,18 +267,6 @@ //! - [`from_parts()`](sp_arithmetic::PerThing::from_parts()) //! //! Each of these can be used to construct and represent ratios within our runtime. -//! -//! #### Fixed Point Arithmetic with [`PerThing`](sp_arithmetic::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/reference_docs/safe_defensive_programming.rs", percent_mult)] -//! -//! With the knowledge of how these types operate in relation to other numbers, let's explore how -//! they're used in Substrate development. -//! -//! ### Fixed Point Math in Substrate Development - Further Context -//! //! You will find types like [`Perbill`](sp_arithmetic::Perbill) being used often in pallet //! development. [`pallet_referenda`] is a good example of a pallet which makes good use of fixed //! point arithmetic to calculate @@ -277,105 +278,166 @@ //! //! For most cases, `Perbill` gives us a reasonable amount of precision for most applications, which //! is why we're using it here. +//! +//! #### Fixed Point Arithmetic with [`PerThing`](sp_arithmetic::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/reference_docs/safe_defensive_programming.rs", percent_mult)] +//! +//! +//! ### Fixed Point Arithmetic in Practice +//! +//! todo : explain +//! +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + fixed_u64_block_computation_example +)] +//! +//! +//! #[cfg(test)] mod tests { - enum BlockchainError { - Overflow, - } + enum BlockchainError { + Overflow, + } + + type Address = (); + + struct Runtime; + + impl Runtime { + fn get_balance(account: Address) -> u64 { + 0 + } + } + + #[docify::export] + fn naive_add(x: u8, y: u8) -> u8 { + x + y + } + + #[docify::export] + fn checked_add_example() { + // This is valid, as 20 is perfectly within the bounds of u32. + let add = (10u32).checked_add(10); + assert_eq!(add, Some(20)) + } + + #[docify::export] + fn checked_add_handle_error_example() { + // This is invalid - we are adding something to the max of u32::MAX, which would overflow. + // Luckily, checked_add just marks this as None! + let add = u32::MAX.checked_add(10); + assert_eq!(add, None) + } + + #[docify::export] + fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + if let Some(new_balance) = balance.checked_add(amount) { + Runtime::set_balance(account, new_balance); + return Ok(()); + } else { + return Err(BlockchainError::Overflow); + } + } + + #[docify::export] + fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + let new_balance = match balance.checked_add(amount) { + Some(balance) => balance, + None => { + return Err(BlockchainError::Overflow); + } + }; + Runtime::set_balance(account, new_balance); + Ok(()) + } - type Address = &'static str; + #[docify::export] + fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount - this time, by using `ok_or` + let new_balance = balance.checked_add(amount).ok_or_else(|| BlockchainError::Overflow)?; + Runtime::set_balance(account, new_balance); + Ok(()) + } - struct Runtime; + #[docify::export] + fn saturated_add_example() { + // Saturating add simply saturates + // to the numeric bound of that type if it overflows. + let add = u32::MAX.saturating_add(10); + assert_eq!(add, u32::MAX) + } + #[docify::export] + 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] + 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); + } - impl Runtime { - fn get_balance(account: Address) -> u64 { - 0 - } - } + #[docify::export] + fn percent_example() { + let percent = Percent::from_rational(190u32, 400u32); + assert_eq!(percent.deconstruct(), 47); + } - #[docify::export] - fn naive_add(x: u8, y: u8) -> u8 { - x + y - } + #[docify::export] + fn fixed_u64_block_computation_example() { + // Cores available per block + let supply = 10u128; + // Cores being ordered per block + let demand = 5u128; + // Calculate a very rudimentry on-chain price from supply / demand + let price = FixedU64::from_rational(demand, supply); - #[docify::export] - fn checked_add_example() { - // This is valid, as 20 is perfectly within the bounds of u32. - let add = (10u32).checked_add(10); - assert_eq!(add, Some(20)) - } + // 0.5 DOT per core + assert_eq!(price, FixedU64::from_float(0.5)); - #[docify::export] - fn checked_add_handle_error_example() { - // This is invalid - we are adding something to the max of u32::MAX, which would overflow. - // Luckily, checked_add just marks this as None! - let add = u32::MAX.checked_add(10); - assert_eq!(add, None) - } + // 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 - #[docify::export] - fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - if let Some(new_balance) = balance.checked_add(amount) { - Runtime::set_balance(account, new_balance); - return Ok(()); - } else { - return Err(BlockchainError::Overflow); - } - } + // Cores available per block + let supply = 10u128; + // Cores being ordered per block + let demand = 19u128; + // Calculate a very rudimentry on-chain price from supply / demand + let price = FixedU64::from_rational(demand, supply); - #[docify::export] - fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - let new_balance = match balance.checked_add(amount) { - Some(balance) => balance, - None => { - return Err(BlockchainError::Overflow); - }, - }; - Runtime::set_balance(account, new_balance); - Ok(()) - } + // 1.9 DOT per core + assert_eq!(price, FixedU64::from_float(1.9)); + } - #[docify::export] - fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - this time, by using `ok_or` - let new_balance = balance.checked_add(amount).ok_or_else(|| BlockchainError::Overflow)?; - Runtime::set_balance(account, new_balance); - Ok(()) - } + #[docify::export] + 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 - #[docify::export] - fn saturated_add_example() { - // Saturating add simply saturates - // to the numeric bound of that type if it overflows. - let add = u32::MAX.saturating_add(10); - assert_eq!(add, u32::MAX) - } - #[docify::export] - 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] - 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); - } + let rational_1 = FixedU64::from_rational(10, 5); //" 200%" aka 2. + let rational_2 = FixedU64::from_rational_with_rounding( + 5, + 10, + sp_arithmetic::Rounding::Down + ); // "50%" aka 0.50... - #[docify::export] - fn percent_example() { - let percent = Percent::from_rational(190u32, 400u32); - assert_eq!(percent.deconstruct(), 47); - } + assert_eq!(rational_1, (2u64).into()); + assert_eq!(rational_2.into_perbill(), Perbill::from_float(0.5)); + } } From d921b7e81a64130f2e8bbe74f9cfec86a6f57f1d Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Thu, 9 Nov 2023 09:33:19 -0500 Subject: [PATCH 10/29] fmt + add fixed examples --- Cargo.lock | 1 + developer-hub/Cargo.toml | 1 + .../safe_defensive_programming.rs | 307 ++++++++++-------- .../src/generic/unchecked_extrinsic.rs | 1 - 4 files changed, 169 insertions(+), 141 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8418fc8c03e..f1abcbb52c28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4632,6 +4632,7 @@ dependencies = [ "frame", "kitchensink-runtime", "pallet-aura", + "pallet-broker", "pallet-default-config-example", "pallet-examples", "pallet-referenda", diff --git a/developer-hub/Cargo.toml b/developer-hub/Cargo.toml index 970733706dd2..d378b1270a8f 100644 --- a/developer-hub/Cargo.toml +++ b/developer-hub/Cargo.toml @@ -20,6 +20,7 @@ pallet-default-config-example = { path = "../substrate/frame/examples/default-co # Extra example pallets pallet-referenda = { path = "../substrate/frame/referenda" } +pallet-broker = { path = "../substrate/frame/broker" } # How we build docs in rust-docs simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", branch = "main" } diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 4204d7ebf24a..649f4c1e8cfe 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -139,6 +139,12 @@ //! 2. **Saturated** operations - limited to the lower and upper bounds of a number type, //! 3. **Wrapped** operations (the default) - wrap around to above or below the bounds of a type, //! +//! +//! Known scenarios that could be fallible should be avoided: i.e., avoiding the possibility of +//! dividing/modulo by zero at any point should be mitigated. One should be, instead, opting for a +//! `checked_` method in order to introduce safe arithmetic in their code. +//! +//! //! #### The problem with 'default' wrapped operations //! //! **Wrapped operations** cause the overflow to wrap around to either the maximum or minimum of @@ -224,7 +230,8 @@ //! _represents_ a floating point number in base 10, i.e., a **fixed point number**, can be used //! instead. //! -//! For use cases which operate within the range of `[0, 1]` types which implement [`PerThing`](sp_arithmetic::PerThing) are used: +//! For use cases which operate within the range of `[0, 1]` types which implement +//! [`PerThing`](sp_arithmetic::PerThing) are used: //! - **[`Perbill`](sp_arithmetic::Perbill), parts of a billion** #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", perbill_example)] //! @@ -232,21 +239,23 @@ #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_example)] //! //! Note that `190 / 400 = 0.475`, and that `Percent` represents it as a _rounded down_, fixed point -//! number (`47`). Unlike primitive types, types that implement [`PerThing`](sp_arithmetic::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. +//! number (`47`). Unlike primitive types, types that implement +//! [`PerThing`](sp_arithmetic::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 a number of other fixed-point types to use: +//! For use cases which require precision beyond the range of `[0, 1]`, there are a number of other +//! fixed-point types to use: //! //! - [`FixedU64`](sp_arithmetic::FixedU64) and [`FixedI64`](sp_arithmetic::FixedI64) //! - [`FixedI128`](sp_arithmetic::FixedU128) and [`FixedU128`](sp_arithmetic::FixedI128) //! -//! Similar to types that implement [`PerThing`](sp_arithmetic::PerThing), these are also fixed-point types, however, they are able to represent larger numbers: -//! +//! Similar to types that implement [`PerThing`](sp_arithmetic::PerThing), these are also +//! fixed-point types, however, they are able to represent larger numbers: #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", fixed_u64)] //! -//! Let's now explore these types in practice, and how they may be used with pallets to perform safer calculations in the runtime. +//! Let's now explore these types in practice, and how they may be used with pallets to perform +//! safer calculations in the runtime. //! //! ### 'PerThing' In Practice //! @@ -259,13 +268,6 @@ //! - [`Permill`](sp_arithmetic::Permill) - parts of a million. //! - [`Perbill`](sp_arithmetic::Perbill) - parts of a billion. //! -//! Because each of these implement the same trait, `PerThing`, we have access to a few widely used -//! methods: -//! -//! - [`from_rational()`](sp_arithmetic::PerThing::from_rational()) -//! - [`from_percent()`](sp_arithmetic::PerThing::from_percent()) -//! - [`from_parts()`](sp_arithmetic::PerThing::from_parts()) -//! //! Each of these can be used to construct and represent ratios within our runtime. //! You will find types like [`Perbill`](sp_arithmetic::Perbill) being used often in pallet //! development. [`pallet_referenda`] is a good example of a pallet which makes good use of fixed @@ -286,158 +288,183 @@ #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_mult)] //! //! -//! ### Fixed Point Arithmetic in Practice -//! -//! todo : explain +//! ### Fixed Point Types in Practice //! +//! As said earlier, if one needs to exceed the value of one, then +//! [`FixedU64`](sp_arithmetic::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/reference_docs/safe_defensive_programming.rs", fixed_u64_block_computation_example )] //! +//! For a much more comprehensive example, be sure to look at the source for [`pallet_broker`] //! +//! #### Fixed Point Types in Practice +//! +//! Just as with [`PerThing`](sp_arithmetic::PerThing), you can also perform regular mathematical +//! expressions: +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + fixed_u64_operation_example +)] //! #[cfg(test)] mod tests { - enum BlockchainError { - Overflow, - } + enum BlockchainError { + Overflow, + } + + type Address = (); + + struct Runtime; + + impl Runtime { + fn get_balance(account: Address) -> u64 { + 0 + } + } - type Address = (); + #[docify::export] + fn naive_add(x: u8, y: u8) -> u8 { + x + y + } - struct Runtime; + #[docify::export] + fn checked_add_example() { + // This is valid, as 20 is perfectly within the bounds of u32. + let add = (10u32).checked_add(10); + assert_eq!(add, Some(20)) + } - impl Runtime { - fn get_balance(account: Address) -> u64 { - 0 - } - } + #[docify::export] + fn checked_add_handle_error_example() { + // This is invalid - we are adding something to the max of u32::MAX, which would overflow. + // Luckily, checked_add just marks this as None! + let add = u32::MAX.checked_add(10); + assert_eq!(add, None) + } - #[docify::export] - fn naive_add(x: u8, y: u8) -> u8 { - x + y - } + #[docify::export] + fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + if let Some(new_balance) = balance.checked_add(amount) { + Runtime::set_balance(account, new_balance); + return Ok(()); + } else { + return Err(BlockchainError::Overflow); + } + } - #[docify::export] - fn checked_add_example() { - // This is valid, as 20 is perfectly within the bounds of u32. - let add = (10u32).checked_add(10); - assert_eq!(add, Some(20)) - } + #[docify::export] + fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount + let new_balance = match balance.checked_add(amount) { + Some(balance) => balance, + None => { + return Err(BlockchainError::Overflow); + }, + }; + Runtime::set_balance(account, new_balance); + Ok(()) + } - #[docify::export] - fn checked_add_handle_error_example() { - // This is invalid - we are adding something to the max of u32::MAX, which would overflow. - // Luckily, checked_add just marks this as None! - let add = u32::MAX.checked_add(10); - assert_eq!(add, None) - } + #[docify::export] + fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { + // Get a user's current balance + let balance = Runtime::get_balance(account)?; + // SAFELY increase the balance by some amount - this time, by using `ok_or` + let new_balance = balance.checked_add(amount).ok_or_else(|| BlockchainError::Overflow)?; + Runtime::set_balance(account, new_balance); + Ok(()) + } - #[docify::export] - fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - if let Some(new_balance) = balance.checked_add(amount) { - Runtime::set_balance(account, new_balance); - return Ok(()); - } else { - return Err(BlockchainError::Overflow); - } - } + #[docify::export] + fn saturated_add_example() { + // Saturating add simply saturates + // to the numeric bound of that type if it overflows. + let add = u32::MAX.saturating_add(10); + assert_eq!(add, u32::MAX) + } + #[docify::export] + 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] + 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] - fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - let new_balance = match balance.checked_add(amount) { - Some(balance) => balance, - None => { - return Err(BlockchainError::Overflow); - } - }; - Runtime::set_balance(account, new_balance); - Ok(()) - } + #[docify::export] + fn percent_example() { + let percent = Percent::from_rational(190u32, 400u32); + assert_eq!(percent.deconstruct(), 47); + } - #[docify::export] - fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { - // Get a user's current balance - let balance = Runtime::get_balance(account)?; - // SAFELY increase the balance by some amount - this time, by using `ok_or` - let new_balance = balance.checked_add(amount).ok_or_else(|| BlockchainError::Overflow)?; - Runtime::set_balance(account, new_balance); - Ok(()) - } + #[docify::export] + fn fixed_u64_block_computation_example() { + // Cores available per block + let supply = 10u128; + // Cores being ordered per block + let demand = 5u128; + // Calculate a very rudimentry on-chain price from supply / demand + let price = FixedU64::from_rational(demand, supply); - #[docify::export] - fn saturated_add_example() { - // Saturating add simply saturates - // to the numeric bound of that type if it overflows. - let add = u32::MAX.saturating_add(10); - assert_eq!(add, u32::MAX) - } - #[docify::export] - 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] - 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); - } + // 0.5 DOT per core + assert_eq!(price, FixedU64::from_float(0.5)); - #[docify::export] - fn percent_example() { - let percent = Percent::from_rational(190u32, 400u32); - assert_eq!(percent.deconstruct(), 47); - } + // 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 - #[docify::export] - fn fixed_u64_block_computation_example() { - // Cores available per block - let supply = 10u128; - // Cores being ordered per block - let demand = 5u128; - // Calculate a very rudimentry on-chain price from supply / demand - let price = FixedU64::from_rational(demand, supply); + // Cores available per block + let supply = 10u128; + // Cores being ordered per block + let demand = 19u128; + // Calculate a very rudimentary on-chain price from supply / demand + let price = FixedU64::from_rational(demand, supply); - // 0.5 DOT per core - assert_eq!(price, FixedU64::from_float(0.5)); + // 1.9 DOT per core + assert_eq!(price, FixedU64::from_float(1.9)); + } - // 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 + #[docify::export] + 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 - // Cores available per block - let supply = 10u128; - // Cores being ordered per block - let demand = 19u128; - // Calculate a very rudimentry on-chain price from supply / demand - let price = FixedU64::from_rational(demand, supply); + let rational_1 = FixedU64::from_rational(10, 5); //" 200%" aka 2. + let rational_2 = + FixedU64::from_rational_with_rounding(5, 10, sp_arithmetic::Rounding::Down); // "50%" aka 0.50... - // 1.9 DOT per core - assert_eq!(price, FixedU64::from_float(1.9)); - } + assert_eq!(rational_1, (2u64).into()); + assert_eq!(rational_2.into_perbill(), Perbill::from_float(0.5)); + } - #[docify::export] - 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 + #[docify::export] + 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 rational_1 = FixedU64::from_rational(10, 5); //" 200%" aka 2. - let rational_2 = FixedU64::from_rational_with_rounding( - 5, - 10, - sp_arithmetic::Rounding::Down - ); // "50%" aka 0.50... + 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!(rational_1, (2u64).into()); - assert_eq!(rational_2.into_perbill(), Perbill::from_float(0.5)); - } + 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)); + } } diff --git a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs index 92d8e1e34698..6e21672c641e 100644 --- a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -57,7 +57,6 @@ type UncheckedSignaturePayload = (Address, Signature, /// could in principle be any other interaction. Transactions are either signed, or unsigned. A /// sensible transaction pool should ensure that only transactions that are worthwhile are /// considered for block-building. -/// #[doc = simple_mermaid::mermaid!("../../../../../docs/mermaid/extrinsics.mmd")] /// This type is by no means enforced within substrate, but given its generic-ness, it is highly /// likely that for most use-cases it will suffice. Thus, the encoding of this type will dictate From 01ae609fa3ebcbfbfc2828781661936ac0ee1e04 Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Thu, 9 Nov 2023 15:52:56 -0500 Subject: [PATCH 11/29] add beginning of general info --- .../safe_defensive_programming.rs | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 649f4c1e8cfe..2dac5132b672 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -1,7 +1,50 @@ -//! As our runtime should _never_ panic; this includes eliminating the possibility of integer +//! As our runtime should _never_ panic; this includes carefully handling [`Result`]/[`Option`] types, eliminating the possibility of integer //! overflows, converting between number types, or even handling floating point usage with fixed //! point arithmetic to mitigate issues that come with floating point calculations. //! +//! +//! ## Defensive Programming +//! +//! Defensive programming is a design paradigm that enables a particular program to continue +//! running despite unexpected behavior. Where normally these unforseen circumstances may +//! cause the program to stop, (or in the Rust context, `panic!`) defensive practices allow for +//! these circumstances to be accounted for ahead of time. +//! +//! The Polkadot SDK is both built to reflect these principles, and to be used accordingly. +//! +//! ## General Practices +//! +//! When developing within the context of the a runtime, there is one golden rule: +//! +//! ***DO NOT PANIC!*** +//! +//! Most of the time, unless you wish for your node to be intentionally brought down - panicking is +//! something that your runtime should feverishly protect against. This includes the following +//! considerations: +//! +//! - Directly using `unwrap()` for a [`Result`] shouldn't be used. +//! - This includes accessing indices of some collection type, which may implicitly `panic!` +//! - It may be acceptable to use `except()`, but only if one is completely certain (and has +//! performed a check beforehand) that a value won't panic upon unwrapping. +//! - If you are writing a function that could panic, [be sure to document it!](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#documenting-components) +//! - Many seemingly, simplistic operations, such as arithmetic in the runtime, could present a +//! number of issues (see more later in this document). +//! +//! ### General Practices - Examples +//! +//! Below are examples of the above concepts in action - it will show what *problematic* code looks +//! like, versus proper form. +//! +//! todo: examples of general defensive practices +//! +//! ### Defensive Traits +//! +//! To also aid in debugging and mitigating the above issues, there is a +//! [`Defensive`](frame::traits::Defensive) trait that can be used to defensively unwrap values. It +//! panics in tests, but in production, will log an error. This can be used in place of an +//! `expect`, and again, only if the developer is sure about the unwrap in the first place. +//! +//! //! ## Integer Overflow //! //! The Rust compiler prevents any sort of static overflow from happening at compile time, for @@ -182,14 +225,14 @@ //!
//! Solution: Saturating //! For Alice's balance problem, using `saturated_sub` could've mitigated this issue. As debt or -//! having a negative balance is not a concept within blockchains, a saturating calculation would've -//! simply limited her balance to the lower bound of u32. +//! having a negative balance is not a concept within blockchains, a saturating calculation +//! would've simply limited her balance to the lower bound of u32. //! //! In other words: Alice's balance would've stayed at "0", even after being slashed. //! //! This is also an example that while one system may work in isolation, shared interfaces, such -//! as the notion of balances, are often shared across multiple pallets - meaning these small -//! changes can make a big difference in outcome.
+//! as the notion of balances, are often shared across multiple pallets - meaning these small +//! changes can make a big difference in outcome.
//! //! #### Proposals' ID Overwrite //! @@ -310,6 +353,10 @@ fixed_u64_operation_example )] //! +//! +//! ## Other Resources +//! +//! - [PBA Book - FRAME Tips & Tricks](https://polkadot-blockchain-academy.github.io/pba-book/substrate/tips-tricks/page.html?highlight=perthing#substrate-and-frame-tips-and-tricks) #[cfg(test)] mod tests { From c8dbb45292bd9de7c943079e3411586627d35cf2 Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Mon, 13 Nov 2023 11:04:58 -0500 Subject: [PATCH 12/29] add examples --- .../safe_defensive_programming.rs | 87 ++++++++++++++++--- 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 2dac5132b672..b1af4aee3acd 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -1,14 +1,16 @@ -//! As our runtime should _never_ panic; this includes carefully handling [`Result`]/[`Option`] types, eliminating the possibility of integer -//! overflows, converting between number types, or even handling floating point usage with fixed -//! point arithmetic to mitigate issues that come with floating point calculations. +//! As our runtime should _never_ panic; this includes carefully handling [`Result`]/[`Option`] +//! types, eliminating the possibility of integer overflows, converting between number types, or +//! even handling floating point usage with fixed point arithmetic to mitigate issues that come with +//! floating point calculations. //! //! //! ## Defensive Programming //! //! Defensive programming is a design paradigm that enables a particular program to continue -//! running despite unexpected behavior. Where normally these unforseen circumstances may +//! running despite unexpected behavior. These unforseen circumstances may //! cause the program to stop, (or in the Rust context, `panic!`) defensive practices allow for -//! these circumstances to be accounted for ahead of time. +//! these circumstances to be accounted for ahead of time, and for them to be handled in a more +//! graceful manner. //! //! The Polkadot SDK is both built to reflect these principles, and to be used accordingly. //! @@ -27,23 +29,59 @@ //! - It may be acceptable to use `except()`, but only if one is completely certain (and has //! performed a check beforehand) that a value won't panic upon unwrapping. //! - If you are writing a function that could panic, [be sure to document it!](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#documenting-components) -//! - Many seemingly, simplistic operations, such as arithmetic in the runtime, could present a -//! number of issues (see more later in this document). +//! - Many seemingly, simplistic operations, such as **arithmetic** in the runtime, could present a +//! number of issues [(see more later in this document)](#integer-overflow). //! //! ### General Practices - Examples //! //! Below are examples of the above concepts in action - it will show what *problematic* code looks //! like, versus proper form. //! -//! todo: examples of general defensive practices +//! The following presents a rather obvious issue - one should always use the default of the type, +//! or handle the error accordingly: +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + bad_unwrap +)] +//! +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + good_unwrap +)] +//! +//! Other operations, such as indexing a vector (or a similar scenario like looping and accessing it +//! in a similar fashion) must also be tread with caution: +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + bad_collection_retrieval +)] +//! +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + good_collection_retrieval +)] +//! //! //! ### Defensive Traits //! //! To also aid in debugging and mitigating the above issues, there is a -//! [`Defensive`](frame::traits::Defensive) trait that can be used to defensively unwrap values. It -//! panics in tests, but in production, will log an error. This can be used in place of an -//! `expect`, and again, only if the developer is sure about the unwrap in the first place. +//! [`Defensive`](frame::traits::Defensive) trait (and its companions, +//! [`DefensiveOption`](frame::traits::DefensiveOption), +//! [`DefensiveResult`](frame::traits::DefensiveResult))that can be used to defensively unwrap +//! values. This can be used in place of +//! an `expect`, and again, only if the developer is sure about the unwrap in the first place. +//! +//! The [`Defensive`](frame::traits::Defensive) trait provides a number of functions, all of which +//! provide an alternative to 'vanilla' Rust functions: +//! +//! - [`defensive_unwrap_or()`](frame::traits::Defensive::defensive_unwrap_or) +//! - [`defensive_ok_or()`](frame::traits::DefensiveOption::defensive_ok_or) +//! +//! The primary difference here is when `debug_assertions` are enabled, that they panic, but in +//! production/release, they will merely log an error via the logging instance the runtime is using +//! (i.e., `log::error`). //! +//! This is useful for testing, but also allows for a more failsafe way to mitigate panics. //! //! ## Integer Overflow //! @@ -514,4 +552,31 @@ mod tests { assert_eq!(division, FixedU64::from_float(1.25)); assert_eq!(subtraction, FixedU64::from_float(0.4)); } + #[docify::export] + fn bad_unwrap() { + let some_result: Result = Ok(10); + assert_eq!(some_result.unwrap(), 10); + } + + #[docify::export] + fn good_unwrap() { + let some_result: Result = Err("Error"); + assert_eq!(some_result.unwrap_or_default(), 0); + assert_eq!(some_result.unwrap_or(10), 10); + } + + #[docify::export] + fn bad_collection_retrieval() { + let my_list = vec![1, 2, 3, 4, 5]; + // THIS PANICS! + // Indexing on heap allocated values, i.e., vec, can be unsafe! + assert_eq!(my_list[5], 6) + } + + #[docify::export] + fn good_collection_retrieval() { + let my_list = vec![1, 2, 3, 4, 5]; + // Rust includes `.get`, returning Option - so lets use that: + assert_eq!(my_list.get(5), None) + } } From bd47256b07c3cdff0b6b30f2cbb18979aae19606 Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Mon, 13 Nov 2023 11:44:17 -0500 Subject: [PATCH 13/29] def saturated add example --- .../safe_defensive_programming.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index b1af4aee3acd..c22b9cf504af 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -212,6 +212,15 @@ //! Saturating calculations can be used if one is very sure that something won't overflow, but wants //! to avoid introducing the notion of any potential-panic or wrapping behavior. //! +//! There is also a series of defensive alternatives via +//! [`DefensiveSaturating`](frame::traits::DefensiveSaturating), which introduces the same behavior +//! of the [`Defensive`](frame::traits::Defensive) trait, only with saturating, mathematical +//! operations: +#![doc = docify::embed!( + "./src/reference_docs/safe_defensive_programming.rs", + saturated_defensive_example +)] +//! //! ### Mathematical Operations in Substrate Development - Further Context //! //! As a recap, we covered the following concepts: @@ -413,11 +422,13 @@ mod tests { } #[docify::export] + #[test] fn naive_add(x: u8, y: u8) -> u8 { x + y } #[docify::export] + #[test] fn checked_add_example() { // This is valid, as 20 is perfectly within the bounds of u32. let add = (10u32).checked_add(10); @@ -425,6 +436,7 @@ mod tests { } #[docify::export] + #[test] fn checked_add_handle_error_example() { // This is invalid - we are adding something to the max of u32::MAX, which would overflow. // Luckily, checked_add just marks this as None! @@ -433,6 +445,7 @@ mod tests { } #[docify::export] + #[test] fn increase_balance(account: Address, amount: u64) -> Result<(), BlockchainError> { // Get a user's current balance let balance = Runtime::get_balance(account)?; @@ -446,6 +459,7 @@ mod tests { } #[docify::export] + #[test] fn increase_balance_match(account: Address, amount: u64) -> Result<(), BlockchainError> { // Get a user's current balance let balance = Runtime::get_balance(account)?; @@ -461,6 +475,7 @@ mod tests { } #[docify::export] + #[test] fn increase_balance_result(account: Address, amount: u64) -> Result<(), BlockchainError> { // Get a user's current balance let balance = Runtime::get_balance(account)?; @@ -471,6 +486,7 @@ mod tests { } #[docify::export] + #[test] fn saturated_add_example() { // Saturating add simply saturates // to the numeric bound of that type if it overflows. @@ -478,12 +494,14 @@ mod tests { assert_eq!(add, u32::MAX) } #[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. @@ -492,12 +510,14 @@ mod tests { } #[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() { // Cores available per block let supply = 10u128; @@ -525,6 +545,7 @@ mod tests { } #[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 @@ -538,6 +559,7 @@ mod tests { } #[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. @@ -553,12 +575,14 @@ mod tests { assert_eq!(subtraction, FixedU64::from_float(0.4)); } #[docify::export] + #[test] fn bad_unwrap() { let some_result: Result = Ok(10); assert_eq!(some_result.unwrap(), 10); } #[docify::export] + #[test] fn good_unwrap() { let some_result: Result = Err("Error"); assert_eq!(some_result.unwrap_or_default(), 0); @@ -566,6 +590,7 @@ mod tests { } #[docify::export] + #[test] fn bad_collection_retrieval() { let my_list = vec![1, 2, 3, 4, 5]; // THIS PANICS! @@ -574,9 +599,17 @@ mod tests { } #[docify::export] + #[test] fn good_collection_retrieval() { let my_list = vec![1, 2, 3, 4, 5]; // Rust includes `.get`, returning Option - so lets use that: assert_eq!(my_list.get(5), None) } + #[docify::export] + #[test] + #[should_panic(expected = "Defensive failure has been triggered!")] + fn saturated_defensive_example() { + let saturated_defensive = u32::MAX.defensive_saturating_add(10); + assert_eq!(saturated_defensive, u32::MAX); + } } From dbde9748b7c149fdd0ff2b07fce4f30f59b572d4 Mon Sep 17 00:00:00 2001 From: bader y Date: Mon, 20 Nov 2023 13:38:15 -0500 Subject: [PATCH 14/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Liam Aharon --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index c22b9cf504af..7e7b1e52fb73 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -81,7 +81,7 @@ //! production/release, they will merely log an error via the logging instance the runtime is using //! (i.e., `log::error`). //! -//! This is useful for testing, but also allows for a more failsafe way to mitigate panics. +//! This is useful for catching issues in the development environment, without risking panicing in production. //! //! ## Integer Overflow //! From 413c05e48a555d4f52a0f7532913b3c0be97bcaf Mon Sep 17 00:00:00 2001 From: bader y Date: Mon, 20 Nov 2023 13:38:33 -0500 Subject: [PATCH 15/29] Update developer-hub/src/polkadot_sdk/frame_runtime.rs Co-authored-by: Juan Girini --- developer-hub/src/polkadot_sdk/frame_runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/polkadot_sdk/frame_runtime.rs b/developer-hub/src/polkadot_sdk/frame_runtime.rs index 50c8133fde75..27d82d3ebad0 100644 --- a/developer-hub/src/polkadot_sdk/frame_runtime.rs +++ b/developer-hub/src/polkadot_sdk/frame_runtime.rs @@ -15,7 +15,7 @@ //! //! ## Introduction //! -//! As described in [`crate::reference_docs::wasm_meta_protocol`], at a high-level substrate-based +//! As described in [`crate::reference_docs::wasm_meta_protocol`], at a high-level Substrate-based //! blockchains are composed of two parts: //! //! 1. A *runtime* which represents the state transition function (i.e. "Business Logic") of a From eccbf52a3d9f98a0d72a60f468f1c0927d2bc48f Mon Sep 17 00:00:00 2001 From: bader y Date: Mon, 20 Nov 2023 13:38:43 -0500 Subject: [PATCH 16/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Liam Aharon --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 7e7b1e52fb73..3d3dc76a761a 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -226,7 +226,7 @@ //! As a recap, we covered the following concepts: //! //! 1. **Checked** operations - using [`Option`] or [`Result`], -//! 2. **Saturated** operations - limited to the lower and upper bounds of a number type, +//! 2. **Saturating** operations - limited to the lower and upper bounds of a number type, //! 3. **Wrapped** operations (the default) - wrap around to above or below the bounds of a type, //! //! From f589d62269434fa6f05dc248454a17acb26183b5 Mon Sep 17 00:00:00 2001 From: bader y Date: Mon, 20 Nov 2023 13:38:52 -0500 Subject: [PATCH 17/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Liam Aharon --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 3d3dc76a761a..123bc32acad6 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -200,7 +200,7 @@ )] //! //! -//! ### Saturated Operations +//! ### Saturating Operations //! //! Saturating a number limits it to the type's upper or lower bound, no matter the integer would //! overflow in runtime. For example, adding to `u32::MAX` would simply limit itself to `u32::MAX`: From 5917fbe12a60923548e57c4dc2a15c50c2eb24b6 Mon Sep 17 00:00:00 2001 From: bader y Date: Mon, 20 Nov 2023 13:39:04 -0500 Subject: [PATCH 18/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Liam Aharon --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 123bc32acad6..df96528bf35e 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -148,7 +148,7 @@ //! The following methods represent different ways one can handle numbers safely natively in Rust, //! without fear of panic or unexpected behavior from wrapping. //! -//! ### Checked Operations +//! ### Checked Arithmetic //! //! **Checked operations** utilize a `Option` as a return type. This allows for simple pattern //! matching to catch any unexpected behavior in the event of an overflow. From 9d96a98259ca5bce4841bfc9b3ac245ae1f4b949 Mon Sep 17 00:00:00 2001 From: bader y Date: Mon, 20 Nov 2023 13:39:24 -0500 Subject: [PATCH 19/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Liam Aharon --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index df96528bf35e..aae82b715566 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -67,7 +67,7 @@ //! To also aid in debugging and mitigating the above issues, there is a //! [`Defensive`](frame::traits::Defensive) trait (and its companions, //! [`DefensiveOption`](frame::traits::DefensiveOption), -//! [`DefensiveResult`](frame::traits::DefensiveResult))that can be used to defensively unwrap +//! [`DefensiveResult`](frame::traits::DefensiveResult)) that can be used to defensively unwrap //! values. This can be used in place of //! an `expect`, and again, only if the developer is sure about the unwrap in the first place. //! From 25fb0f6cd89235090394e7ce9afb25e6c6cdd040 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:14:04 -0500 Subject: [PATCH 20/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index aae82b715566..3de37d71765f 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -135,7 +135,7 @@ //! //! Our main objective in runtime development is to reduce the likelihood of any *unintended* or //! *undefined* behavior. Intentional and predictable design should be our first and foremost -//! property for ensuring a well running, safely designed system. Both Rust and Substrate both +//! property for ensuring a well running, safely designed system. Both Rust and Substrate //! provide safe ways to deal with numbers and alternatives to floating point arithmetic. //! //! Rather they (should) use fixed-point arithmetic to mitigate the potential for inaccuracy, From b77e5a2ee206fd3f55ea46928e59bc42e03feab2 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:14:17 -0500 Subject: [PATCH 21/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 3de37d71765f..bbadf8354731 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -150,7 +150,7 @@ //! //! ### Checked Arithmetic //! -//! **Checked operations** utilize a `Option` as a return type. This allows for simple pattern +//! **Checked operations** utilize an `Option` as a return type. This allows for simple pattern //! matching to catch any unexpected behavior in the event of an overflow. //! //! This is an example of a valid operation: From eac36f4ebd350ddc75d8c7ef9892e773dd8df8ac Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:14:29 -0500 Subject: [PATCH 22/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index bbadf8354731..5554678b6bd1 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -231,7 +231,7 @@ //! //! //! Known scenarios that could be fallible should be avoided: i.e., avoiding the possibility of -//! dividing/modulo by zero at any point should be mitigated. One should be, instead, opting for a +//! dividing/modulo by zero at any point should be mitigated. One should be, instead, opting for a //! `checked_` method in order to introduce safe arithmetic in their code. //! //! From 9958011c18ab81231c8c4bd0669c6f6d19dd8d76 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:14:36 -0500 Subject: [PATCH 23/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 5554678b6bd1..deb938aca193 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -361,7 +361,7 @@ //! Each of these can be used to construct and represent ratios within our runtime. //! You will find types like [`Perbill`](sp_arithmetic::Perbill) being used often in pallet //! development. [`pallet_referenda`] is a good example of a pallet which makes good use of fixed -//! point arithmetic to calculate +//! point arithmetic to calculate. //! //! Let's examine the usage of `Perbill` and how exactly we can use it as an alternative to floating //! point numbers in Substrate development. For this scenario, let's say we are demonstrating a From 9e42f75f26897c25581d0ac408c1881ccba520d3 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:14:44 -0500 Subject: [PATCH 24/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index deb938aca193..37ca0e067478 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -320,7 +320,7 @@ //! _represents_ a floating point number in base 10, i.e., a **fixed point number**, can be used //! instead. //! -//! For use cases which operate within the range of `[0, 1]` types which implement +//! For use cases which operate within the range of `[0, 1]` types that implement //! [`PerThing`](sp_arithmetic::PerThing) are used: //! - **[`Perbill`](sp_arithmetic::Perbill), parts of a billion** #![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", perbill_example)] From b5e36fa591ee3ff7b8b8d6a9be1a8708a1814c4a Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:15:21 -0500 Subject: [PATCH 25/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 37ca0e067478..bdb7a33051e6 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -304,7 +304,7 @@ //! //! //! From the above, we can clearly see the problematic nature of seemingly simple operations in -//! runtime. Of course, it may be that using checked math is perfectly fine under some scenarios - +//! runtime. Of course, it may be that using unchecked math is perfectly fine under some scenarios - //! such as certain balance being never realistically attainable, or a number type being so large //! that it could never realistically overflow unless one sent thousands of transactions to the //! network. From 77cdda487ee539ddb4adf71f6d0da9f0628f1b42 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:15:28 -0500 Subject: [PATCH 26/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index bdb7a33051e6..09fdf508dcfe 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -292,7 +292,7 @@ //!
//! Solution: Checked //! For the proposal IDs, proper handling via `checked` math would've been much more suitable, -//! Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to +//! Saturating could've been used - but it also would've 'failed' silently. Using `checked_add` to //! ensure that the next proposal ID would've been valid would've been a viable way to let the user //! know the state of their proposal: //! From c6c399c740344686d6c86784cf915f0a5c7caaf6 Mon Sep 17 00:00:00 2001 From: bader y Date: Wed, 22 Nov 2023 12:15:37 -0500 Subject: [PATCH 27/29] Update developer-hub/src/reference_docs/safe_defensive_programming.rs Co-authored-by: Juan Girini --- developer-hub/src/reference_docs/safe_defensive_programming.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs index 09fdf508dcfe..46db2971d200 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/safe_defensive_programming.rs @@ -273,7 +273,7 @@ //! Solution: Saturating //! For Alice's balance problem, using `saturated_sub` could've mitigated this issue. As debt or //! having a negative balance is not a concept within blockchains, a saturating calculation -//! would've simply limited her balance to the lower bound of u32. +//! would've simply limited her balance to the lower bound of u32. //! //! In other words: Alice's balance would've stayed at "0", even after being slashed. //! From c2cd0d50ee6931584d07ec337c4e86b0bf0c1d3f Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Wed, 22 Nov 2023 13:27:19 -0500 Subject: [PATCH 28/29] update name, make more concise --- .../src/guides/your_first_pallet/mod.rs | 6 +- ...rogramming.rs => defensive_programming.rs} | 126 ++++++------------ developer-hub/src/reference_docs/mod.rs | 2 +- 3 files changed, 44 insertions(+), 90 deletions(-) rename developer-hub/src/reference_docs/{safe_defensive_programming.rs => defensive_programming.rs} (84%) diff --git a/developer-hub/src/guides/your_first_pallet/mod.rs b/developer-hub/src/guides/your_first_pallet/mod.rs index 91d5ec4a6422..f032d061afe4 100644 --- a/developer-hub/src/guides/your_first_pallet/mod.rs +++ b/developer-hub/src/guides/your_first_pallet/mod.rs @@ -104,8 +104,8 @@ //! This macro will call `.into()` under the hood. #![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_better)] //! -//! Moreover, you will learn in the [Safe Defensive Programming -//! section](crate::reference_docs::safe_defensive_programming) that it is always recommended to use +//! Moreover, you will learn in the [Defensive Programming +//! section](crate::reference_docs::defensive_programming) that it is always recommended to use //! safe arithmetic operations in your runtime. By using [`frame::traits::CheckedSub`], we can not //! only take a step in that direction, but also improve the error handing and make it slightly more //! ergonomic. @@ -288,7 +288,7 @@ //! The following topics where used in this guide, but not covered in depth. It is suggested to //! study them subsequently: //! -//! - [`crate::reference_docs::safe_defensive_programming`]. +//! - [`crate::reference_docs::defensive_programming`]. //! - [`crate::reference_docs::frame_origin`]. //! - [`crate::reference_docs::frame_composite_enums`]. //! - The pallet we wrote in this guide was using `dev_mode`, learn more in diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/defensive_programming.rs similarity index 84% rename from developer-hub/src/reference_docs/safe_defensive_programming.rs rename to developer-hub/src/reference_docs/defensive_programming.rs index 46db2971d200..4f9fc9bdec72 100644 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ b/developer-hub/src/reference_docs/defensive_programming.rs @@ -3,6 +3,8 @@ //! even handling floating point usage with fixed point arithmetic to mitigate issues that come with //! floating point calculations. //! +//! Intentional and predictable design should be our first and foremost +//! property for ensuring a well running, safely designed system. //! //! ## Defensive Programming //! @@ -20,48 +22,18 @@ //! //! ***DO NOT PANIC!*** //! -//! Most of the time, unless you wish for your node to be intentionally brought down - panicking is -//! something that your runtime should feverishly protect against. This includes the following -//! considerations: +//! Most of the time - there are some exceptions, such as critical operations being actually more +//! dangerous than allowing the node to continue running (block authoring, consensus, etc). //! //! - Directly using `unwrap()` for a [`Result`] shouldn't be used. -//! - This includes accessing indices of some collection type, which may implicitly `panic!` +//! - This includes accessing indices of some collection type, which may implicitly `panic!` (i.e., +//! via `get()`) //! - It may be acceptable to use `except()`, but only if one is completely certain (and has //! performed a check beforehand) that a value won't panic upon unwrapping. //! - If you are writing a function that could panic, [be sure to document it!](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#documenting-components) //! - Many seemingly, simplistic operations, such as **arithmetic** in the runtime, could present a //! number of issues [(see more later in this document)](#integer-overflow). //! -//! ### General Practices - Examples -//! -//! Below are examples of the above concepts in action - it will show what *problematic* code looks -//! like, versus proper form. -//! -//! The following presents a rather obvious issue - one should always use the default of the type, -//! or handle the error accordingly: -#![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", - bad_unwrap -)] -//! -#![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", - good_unwrap -)] -//! -//! Other operations, such as indexing a vector (or a similar scenario like looping and accessing it -//! in a similar fashion) must also be tread with caution: -#![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", - bad_collection_retrieval -)] -//! -#![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", - good_collection_retrieval -)] -//! -//! //! ### Defensive Traits //! //! To also aid in debugging and mitigating the above issues, there is a @@ -71,77 +43,59 @@ //! values. This can be used in place of //! an `expect`, and again, only if the developer is sure about the unwrap in the first place. //! +//! The primary difference of defensive implementations bring over vanilla ones is the usage of [`debug_assertions`](https://doc.rust-lang.org/reference/conditional-compilation.html#debug_assertions). +//! `debug_assertions` allows for panics to occur in a testing context, but in +//! production/release, they will merely log an error (i.e., `log::error`). +//! //! The [`Defensive`](frame::traits::Defensive) trait provides a number of functions, all of which //! provide an alternative to 'vanilla' Rust functions: //! //! - [`defensive_unwrap_or()`](frame::traits::Defensive::defensive_unwrap_or) //! - [`defensive_ok_or()`](frame::traits::DefensiveOption::defensive_ok_or) //! -//! The primary difference here is when `debug_assertions` are enabled, that they panic, but in -//! production/release, they will merely log an error via the logging instance the runtime is using -//! (i.e., `log::error`). -//! -//! This is useful for catching issues in the development environment, without risking panicing in production. +//! This traits are useful for catching issues in the development environment, without risking +//! panicking in production. //! //! ## Integer Overflow //! -//! The Rust compiler prevents any sort of static overflow from happening at compile time, for -//! example: -//! -//! ```ignore -//! let overflow = u8::MAX + 10; -//! ``` -//! Would yield the following error: -//! ```text -//! error: this arithmetic operation will overflow -//! --> src/main.rs:121:24 -//! | -//! 121 | let overflow = u8::MAX + 10; -//! | ^^^^^^^^^^^^ attempt to compute `u8::MAX + 10_u8`, which would overflow -//! | -//! ``` -//! +//! The Rust compiler prevents any sort of static overflow from happening at compile time. +//! The compiler panics in **debug** mode in the event of an integer overflow. In +//! **release** mode, it resorts to silently _wrapping_ the overflowed amount in a modular fashion. //! //! However in the runtime context, we don't always have control over what is being supplied as a //! parameter. For example, even this simple adding function could present one of two outcomes //! depending on whether it is in **release** or **debug** mode: -#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", naive_add)] +#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", naive_add)] //! //! If we passed in overflow-able values at runtime, this could actually panic (or wrap, if in //! release). +//! //! ```ignore //! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4. //! ``` //! -//! The Rust compiler would panic in **debug** mode in the event of an integer overflow. In -//! **release** mode, it resorts to silently _wrapping_ the overflowed amount in a modular fashion, -//! (hence returning `4`). -//! //! It is actually the _silent_ portion of this behavior that presents a real issue - as it may be -//! an unintended, but also a very _silent killer_ in terms of producing bugs. In fact, it would -//! have been better for this type of behavior to produce some sort of error, or even `panic!`, as -//! in that scenario, at least such behavior could become obvious. Especially in the context of -//! blockchain development, where unsafe arithmetic could produce unexpected consequences. +//! an unintended in terms of producing bugs. Such behavior should be made obvious. Especially in +//! the context of blockchain development, where unsafe arithmetic could produce unexpected +//! consequences. //! //! A quick example is a user's balance overflowing: the default behavior of wrapping could result -//! in the user's balance starting from zero, or vice versa, of a `0` balance turning into the `MAX` -//! of some type. Naturally, this could lead to various exploits and issues down the road, which if -//! failing silently, would be difficult to trace and rectify. +//! in the user's balance starting from zero, or vice versa, of a `0` balance turning into the +//! `MAX`. This could lead to various exploits and issues down the road, which if +//! failing silently, would be difficult to trace and rectify in production. //! //! Luckily, there are ways to both represent and handle these scenarios depending on our specific //! use case natively built into Rust, as well as libraries like [`sp_arithmetic`]. //! //! ## Infallible Arithmetic //! -//! Our main objective in runtime development is to reduce the likelihood of any *unintended* or -//! *undefined* behavior. Intentional and predictable design should be our first and foremost -//! property for ensuring a well running, safely designed system. Both Rust and Substrate -//! provide safe ways to deal with numbers and alternatives to floating point arithmetic. +//! Both Rust and Substrate provide safe ways to deal with numbers and alternatives to floating +//! point arithmetic. //! -//! Rather they (should) use fixed-point arithmetic to mitigate the potential for inaccuracy, +//! A developer should use fixed-point arithmetic to mitigate the potential for inaccuracy, //! rounding errors, or other unexpected behavior. For more on the specifics of the peculiarities of floating point calculations, [watch this video by the Computerphile](https://www.youtube.com/watch?v=PZRI1IfStY0). //! -//! Using **primitive** floating point number types in a blockchain context should also be avoided, +//! Using **primitive** floating point number types in a blockchain context should be avoided, //! as a single nondeterministic result could cause chaos for consensus along with the //! aforementioned issues. //! @@ -154,12 +108,12 @@ //! matching to catch any unexpected behavior in the event of an overflow. //! //! This is an example of a valid operation: -#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", checked_add_example)] +#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", checked_add_example)] //! //! This is an example of an invalid operation, in this case, a simulated integer overflow, which //! would simply result in `None`: #![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", + "./src/reference_docs/defensive_programming.rs", checked_add_handle_error_example )] //! @@ -178,11 +132,11 @@ //! //! Because wrapped operations return `Option`, you can use a more verbose/explicit form of error //! handling via `if` or `if let`: -#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", increase_balance)] +#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", increase_balance)] //! //! Optionally, `match` may also be directly used in a more concise manner: #![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", + "./src/reference_docs/defensive_programming.rs", increase_balance_match )] //! @@ -195,7 +149,7 @@ //! `ok_or`. This is a less verbose way of expressing the above. Which to use often boils down to //! the developer's preference: #![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", + "./src/reference_docs/defensive_programming.rs", increase_balance_result )] //! @@ -205,7 +159,7 @@ //! Saturating a number limits it to the type's upper or lower bound, no matter the integer would //! overflow in runtime. For example, adding to `u32::MAX` would simply limit itself to `u32::MAX`: #![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", + "./src/reference_docs/defensive_programming.rs", saturated_add_example )] //! @@ -217,7 +171,7 @@ //! of the [`Defensive`](frame::traits::Defensive) trait, only with saturating, mathematical //! operations: #![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", + "./src/reference_docs/defensive_programming.rs", saturated_defensive_example )] //! @@ -323,10 +277,10 @@ //! For use cases which operate within the range of `[0, 1]` types that implement //! [`PerThing`](sp_arithmetic::PerThing) are used: //! - **[`Perbill`](sp_arithmetic::Perbill), parts of a billion** -#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", perbill_example)] +#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", perbill_example)] //! //! - **[`Percent`](sp_arithmetic::Percent), parts of a hundred** -#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_example)] +#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", percent_example)] //! //! Note that `190 / 400 = 0.475`, and that `Percent` represents it as a _rounded down_, fixed point //! number (`47`). Unlike primitive types, types that implement @@ -342,7 +296,7 @@ //! //! Similar to types that implement [`PerThing`](sp_arithmetic::PerThing), these are also //! fixed-point types, however, they are able to represent larger numbers: -#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", fixed_u64)] +#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", fixed_u64)] //! //! Let's now explore these types in practice, and how they may be used with pallets to perform //! safer calculations in the runtime. @@ -375,7 +329,7 @@ //! //! As stated, one can also perform mathematics using these types directly. For example, finding the //! percentage of a particular item: -#![doc = docify::embed!("./src/reference_docs/safe_defensive_programming.rs", percent_mult)] +#![doc = docify::embed!("./src/reference_docs/defensive_programming.rs", percent_mult)] //! //! //! ### Fixed Point Types in Practice @@ -385,7 +339,7 @@ //! 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/reference_docs/safe_defensive_programming.rs", + "./src/reference_docs/defensive_programming.rs", fixed_u64_block_computation_example )] //! @@ -396,7 +350,7 @@ //! Just as with [`PerThing`](sp_arithmetic::PerThing), you can also perform regular mathematical //! expressions: #![doc = docify::embed!( - "./src/reference_docs/safe_defensive_programming.rs", + "./src/reference_docs/defensive_programming.rs", fixed_u64_operation_example )] //! diff --git a/developer-hub/src/reference_docs/mod.rs b/developer-hub/src/reference_docs/mod.rs index 7c870f06a1b4..432eefcbf932 100644 --- a/developer-hub/src/reference_docs/mod.rs +++ b/developer-hub/src/reference_docs/mod.rs @@ -49,7 +49,7 @@ pub mod frame_origin; /// Learn about how to write safe and defensive code in your FRAME runtime. // TODO: @CrackTheCode016 -pub mod safe_defensive_programming; +pub mod defensive_programming; /// Learn about composite enums in FRAME-based runtimes, such as "RuntimeEvent" and "RuntimeCall". pub mod frame_composite_enums; From 0aa08c2bd9fe7d85819cdba3e29534704c12c2b5 Mon Sep 17 00:00:00 2001 From: Bader Youssef Date: Wed, 22 Nov 2023 13:31:04 -0500 Subject: [PATCH 29/29] wording --- developer-hub/src/reference_docs/defensive_programming.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/developer-hub/src/reference_docs/defensive_programming.rs b/developer-hub/src/reference_docs/defensive_programming.rs index 4f9fc9bdec72..7f186b89e714 100644 --- a/developer-hub/src/reference_docs/defensive_programming.rs +++ b/developer-hub/src/reference_docs/defensive_programming.rs @@ -74,8 +74,7 @@ //! naive_add(250u8, 10u8); // In debug mode, this would panic. In release, this would return 4. //! ``` //! -//! It is actually the _silent_ portion of this behavior that presents a real issue - as it may be -//! an unintended in terms of producing bugs. Such behavior should be made obvious. Especially in +//! It is actually the _silent_ portion of this behavior that presents a real issue. Such behavior should be made obvious, especially in //! the context of blockchain development, where unsafe arithmetic could produce unexpected //! consequences. //!