diff --git a/Cargo.lock b/Cargo.lock index 5691e32c6ac3..b140384a8353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4719,8 +4719,10 @@ dependencies = [ "frame", "kitchensink-runtime", "pallet-aura", + "pallet-broker", "pallet-default-config-example", "pallet-examples", + "pallet-referenda", "pallet-timestamp", "parity-scale-codec", "sc-cli", @@ -4737,6 +4739,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", diff --git a/developer-hub/Cargo.toml b/developer-hub/Cargo.toml index c19d5480ec47..520157c56fe7 100644 --- a/developer-hub/Cargo.toml +++ b/developer-hub/Cargo.toml @@ -18,6 +18,10 @@ 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" } +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" } docify = "0.2.6" @@ -57,6 +61,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/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/defensive_programming.rs b/developer-hub/src/reference_docs/defensive_programming.rs new file mode 100644 index 000000000000..7f186b89e714 --- /dev/null +++ b/developer-hub/src/reference_docs/defensive_programming.rs @@ -0,0 +1,568 @@ +//! 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. +//! +//! Intentional and predictable design should be our first and foremost +//! property for ensuring a well running, safely designed system. +//! +//! ## Defensive Programming +//! +//! Defensive programming is a design paradigm that enables a particular program to continue +//! 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, 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. +//! +//! ## General Practices +//! +//! When developing within the context of the a runtime, there is one golden rule: +//! +//! ***DO NOT PANIC!*** +//! +//! 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!` (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). +//! +//! ### Defensive Traits +//! +//! 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 +//! 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) +//! +//! 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. +//! 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/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. +//! ``` +//! +//! 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. +//! +//! 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`. 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 +//! +//! Both Rust and Substrate provide safe ways to deal with numbers and alternatives to floating +//! point arithmetic. +//! +//! 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 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 Arithmetic +//! +//! **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: +#![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/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 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`]) +//! +//! #### 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/defensive_programming.rs", increase_balance)] +//! +//! Optionally, `match` may also be directly used in a more concise manner: +#![doc = docify::embed!( + "./src/reference_docs/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/defensive_programming.rs", + increase_balance_result +)] +//! +//! +//! ### 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`: +#![doc = docify::embed!( + "./src/reference_docs/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. +//! +//! 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/defensive_programming.rs", + saturated_defensive_example +)] +//! +//! ### Mathematical Operations in Substrate Development - Further Context +//! +//! As a recap, we covered the following concepts: +//! +//! 1. **Checked** operations - using [`Option`] or [`Result`], +//! 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, +//! +//! +//! 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 +//! 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: +//! +//! ```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 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. +//! +//! ### Decision Chart: When to use which? +#![doc = simple_mermaid::mermaid!("../../../docs/mermaid/integer_operation_decision.mmd")] +//! ## Fixed Point 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. +//! +//! 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/defensive_programming.rs", perbill_example)] +//! +//! - **[`Percent`](sp_arithmetic::Percent), parts of a hundred** +#![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 +//! [`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: +//! +//! - [`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/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`](sp_arithmetic::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. +//! +//! 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. +//! +//! 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. +//! +//! #### 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/defensive_programming.rs", percent_mult)] +//! +//! +//! ### 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/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/defensive_programming.rs", + 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 { + enum BlockchainError { + Overflow, + } + + type Address = (); + + struct Runtime; + + impl Runtime { + fn get_balance(account: Address) -> u64 { + 0 + } + } + + #[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); + assert_eq!(add, Some(20)) + } + + #[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! + let add = u32::MAX.checked_add(10); + assert_eq!(add, None) + } + + #[docify::export] + #[test] + 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] + #[test] + 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] + #[test] + 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] + #[test] + 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] + #[test] + fn percent_mult() { + let percent = Percent::from_rational(5u32, 100u32); // aka, 5% + let five_percent_of_100 = percent * 100u32; // 5% of 100 is 5. + assert_eq!(five_percent_of_100, 5) + } + #[docify::export] + #[test] + fn perbill_example() { + let p = Perbill::from_percent(80); + // 800000000 bil, or a representative of 0.800000000. + // Precision is in the billions place. + assert_eq!(p.deconstruct(), 800000000); + } + + #[docify::export] + #[test] + fn percent_example() { + let percent = Percent::from_rational(190u32, 400u32); + assert_eq!(percent.deconstruct(), 47); + } + + #[docify::export] + #[test] + fn fixed_u64_block_computation_example() { + // 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); + + // 0.5 DOT per core + assert_eq!(price, FixedU64::from_float(0.5)); + + // Now, the story has changed - lots of demand means we buy as many cores as there + // available. This also means that price goes up! For the sake of simplicity, we don't care + // about who gets a core - just about our very simple price model + + // 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); + + // 1.9 DOT per core + assert_eq!(price, FixedU64::from_float(1.9)); + } + + #[docify::export] + #[test] + fn fixed_u64() { + // The difference between this and perthings is perthings operates within the relam of [0, + // 1] In cases where we need > 1, we can used fixed types such as FixedU64 + + let rational_1 = FixedU64::from_rational(10, 5); //" 200%" aka 2. + let rational_2 = + FixedU64::from_rational_with_rounding(5, 10, sp_arithmetic::Rounding::Down); // "50%" aka 0.50... + + assert_eq!(rational_1, (2u64).into()); + assert_eq!(rational_2.into_perbill(), Perbill::from_float(0.5)); + } + + #[docify::export] + #[test] + fn fixed_u64_operation_example() { + let rational_1 = FixedU64::from_rational(10, 5); // "200%" aka 2. + let rational_2 = FixedU64::from_rational(8, 5); // "160%" aka 1.6. + + let addition = rational_1 + rational_2; + let multiplication = rational_1 * rational_2; + let division = rational_1 / rational_2; + let subtraction = rational_1 - rational_2; + + assert_eq!(addition, FixedU64::from_float(3.6)); + assert_eq!(multiplication, FixedU64::from_float(3.2)); + assert_eq!(division, FixedU64::from_float(1.25)); + assert_eq!(subtraction, FixedU64::from_float(0.4)); + } + #[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); + assert_eq!(some_result.unwrap_or(10), 10); + } + + #[docify::export] + #[test] + 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] + #[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); + } +} 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; diff --git a/developer-hub/src/reference_docs/safe_defensive_programming.rs b/developer-hub/src/reference_docs/safe_defensive_programming.rs deleted file mode 100644 index 9d0f028e570d..000000000000 --- a/developer-hub/src/reference_docs/safe_defensive_programming.rs +++ /dev/null @@ -1 +0,0 @@ -//! diff --git a/docs/mermaid/integer_operation_decision.mmd b/docs/mermaid/integer_operation_decision.mmd new file mode 100644 index 000000000000..243a856fb694 --- /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, but safety is still desired"]