Use Safe Arithmetic Operations
Severity: High
Description
In Substrate runtime development, uncontrolled overflow or underflow is a critical issue because it can cause the chain to stall. Substrate’s deterministic execution model requires every node in the network to process transactions in the exact same way. If an unchecked arithmetic operation causes an overflow or underflow, the runtime will panic, leading to an unrecoverable state for the affected block.
What should be avoided
The following code performs addition without checking for overflow, which may cause the program to wrap around to an unintended value:
#![allow(unused)] fn main() { // Potential for overflow if a + b exceeds the maximum value of the type. let total: u16 = a + b; }
In this example:
- If
a
andb
are large, the result may exceed the data type’s maximum value, causing an overflow and leading to incorrect results.
Best practice
Option 1: Use Checked Arithmetic
Use checked_add
to return an error if the operation exceeds the maximum value:
#![allow(unused)] fn main() { let total: u16 = a.checked_add(b).ok_or(Error::<T>::Overflow)?; }
In this example:
checked_add
returnsNone
ifa + b
would overflow, allowing us to handle the error withok_or
.- This ensures that the operation will only proceed if the result is within bounds, preventing overflow-related issues.
Option 2: Use Saturating Arithmetic
Alternatively, saturating_add
ensures that the result will cap at the maximum value of the type rather than overflowing:
#![allow(unused)] fn main() { let total: u16 = a.saturating_add(b); }
In this example:
saturating_add
will settotal
to the maximum possible value ifa + b
exceeds the type’s limit.- This approach avoids panics or errors by safely capping the result, which is useful when an upper bound is acceptable in the application logic.
Both methods improve the reliability of arithmetic operations, ensuring predictable behavior without overflow.