diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 734b84ebd9d85..5a13fc4ab6509 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -49,7 +49,7 @@ macro_rules! defensive { ); debug_assert!(false, "{}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR); }; - ($error:tt) => { + ($error:expr $(,)?) => { frame_support::log::error!( target: "runtime", "{}: {:?}", @@ -58,7 +58,7 @@ macro_rules! defensive { ); debug_assert!(false, "{}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error); }; - ($error:tt, $proof:tt) => { + ($error:expr, $proof:expr $(,)?) => { frame_support::log::error!( target: "runtime", "{}: {:?}: {:?}", @@ -70,6 +70,25 @@ macro_rules! defensive { } } +/// Trigger a defensive failure if a condition is not met. +/// +/// Similar to [`assert!`] but will print an error without `debug_assertions` instead of silently +/// ignoring it. Only accepts one instead of variable formatting arguments. +/// +/// # Example +/// +/// ```should_panic +/// frame_support::defensive_assert!(1 == 0, "Must fail") +/// ``` +#[macro_export] +macro_rules! defensive_assert { + ($cond:expr $(, $proof:expr )? $(,)?) => { + if !($cond) { + $crate::defensive!(::core::stringify!($cond) $(, $proof )?); + } + }; +} + /// Prelude module for all defensive traits to be imported at once. pub mod defensive_prelude { pub use super::{Defensive, DefensiveOption, DefensiveResult}; @@ -1141,6 +1160,27 @@ mod test { use sp_core::bounded::{BoundedSlice, BoundedVec}; use sp_std::marker::PhantomData; + #[test] + fn defensive_assert_works() { + defensive_assert!(true); + defensive_assert!(true,); + defensive_assert!(true, "must work"); + defensive_assert!(true, "must work",); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive failure has been triggered!: \"1 == 0\": \"Must fail\"")] + fn defensive_assert_panics() { + defensive_assert!(1 == 0, "Must fail"); + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_assert_does_not_panic() { + defensive_assert!(1 == 0, "Must fail"); + } + #[test] #[cfg(not(debug_assertions))] fn defensive_saturating_accrue_works() {