Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 211 additions & 8 deletions crates/fuzz/src/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,228 @@ macro_rules! invariant {
};
}

/// Checks if two expressions are equal and panics with `InvariantViolation` if not.
/// Checks that two expressions are equal.
///
/// Use this macro to check if two expressions are equal.
/// When the expressions are not equal, it will be counted and collected separately
/// from unexpected panics (bugs in fuzz test code).
/// On failure, displays the actual values of both sides.
///
/// # Examples
///
/// ```ignore
/// // Simple condition check
/// invariant_eq!(balance_after, balance_before - amount);
/// invariant_eq!(account.is_initialized, true, "Account is not initialized");
/// invariant_eq!(account.owner, program_id, "wrong owner");
/// ```
#[macro_export]
macro_rules! invariant_eq {
($a:expr, $b:expr) => {
invariant!($a == $b);
{
let left = &$a;
let right = &$b;
if left != right {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!(
"invariant violation: `{}` == `{}`, got: {:?} vs {:?}",
stringify!($a), stringify!($b), left, right
)
));
}
}
};
($a:expr, $b:expr, $($msg:tt)*) => {
{
let left = &$a;
let right = &$b;
if left != right {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!("{} (got: {:?} vs {:?})", format!($($msg)*), left, right)
));
}
}
};
}

/// Checks that two expressions are not equal.
///
/// On failure, displays the value that both sides unexpectedly share.
///
/// # Examples
///
/// ```ignore
/// invariant_ne!(owner, Pubkey::default());
/// invariant_ne!(balance_after, balance_before, "balance should have changed");
/// ```
#[macro_export]
macro_rules! invariant_ne {
($a:expr, $b:expr) => {
{
let left = &$a;
let right = &$b;
if left == right {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!(
"invariant violation: `{}` != `{}`, but both are: {:?}",
stringify!($a), stringify!($b), left
)
));
}
}
};
($a:expr, $b:expr, $($msg:tt)*) => {
{
let left = &$a;
let right = &$b;
if left == right {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!("{} (both are: {:?})", format!($($msg)*), left)
));
}
}
};
}

/// Checks that the first expression is strictly greater than the second.
///
/// # Examples
///
/// ```ignore
/// invariant_gt!(balance, 0);
/// invariant_gt!(supply_after, supply_before, "supply should increase after mint");
/// ```
#[macro_export]
macro_rules! invariant_gt {
($a:expr, $b:expr) => {
{
let left = &$a;
let right = &$b;
if !(left > right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!(
"invariant violation: `{}` > `{}`, got: {:?} vs {:?}",
stringify!($a), stringify!($b), left, right
)
));
}
}
};
($a:expr, $b:expr, $($msg:tt)*) => {
invariant!($a == $b, $($msg)*);
{
let left = &$a;
let right = &$b;
if !(left > right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!("{} (got: {:?} vs {:?})", format!($($msg)*), left, right)
));
}
}
};
}

/// Checks that the first expression is greater than or equal to the second.
///
/// # Examples
///
/// ```ignore
/// invariant_gte!(balance, minimum_balance);
/// invariant_gte!(lamports, rent_exempt, "account not rent-exempt");
/// ```
#[macro_export]
macro_rules! invariant_gte {
($a:expr, $b:expr) => {
{
let left = &$a;
let right = &$b;
if !(left >= right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!(
"invariant violation: `{}` >= `{}`, got: {:?} vs {:?}",
stringify!($a), stringify!($b), left, right
)
));
}
}
};
($a:expr, $b:expr, $($msg:tt)*) => {
{
let left = &$a;
let right = &$b;
if !(left >= right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!("{} (got: {:?} vs {:?})", format!($($msg)*), left, right)
));
}
}
};
}

/// Checks that the first expression is strictly less than the second.
///
/// # Examples
///
/// ```ignore
/// invariant_lt!(balance_after, balance_before);
/// invariant_lt!(fee, max_fee, "fee exceeds maximum");
/// ```
#[macro_export]
macro_rules! invariant_lt {
($a:expr, $b:expr) => {
{
let left = &$a;
let right = &$b;
if !(left < right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!(
"invariant violation: `{}` < `{}`, got: {:?} vs {:?}",
stringify!($a), stringify!($b), left, right
)
));
}
}
};
($a:expr, $b:expr, $($msg:tt)*) => {
{
let left = &$a;
let right = &$b;
if !(left < right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!("{} (got: {:?} vs {:?})", format!($($msg)*), left, right)
));
}
}
};
}

/// Checks that the first expression is less than or equal to the second.
///
/// # Examples
///
/// ```ignore
/// invariant_lte!(withdrawal, balance);
/// invariant_lte!(total_supply, max_supply, "supply overflow");
/// ```
#[macro_export]
macro_rules! invariant_lte {
($a:expr, $b:expr) => {
{
let left = &$a;
let right = &$b;
if !(left <= right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!(
"invariant violation: `{}` <= `{}`, got: {:?} vs {:?}",
stringify!($a), stringify!($b), left, right
)
));
}
}
};
($a:expr, $b:expr, $($msg:tt)*) => {
{
let left = &$a;
let right = &$b;
if !(left <= right) {
std::panic::panic_any($crate::invariant::InvariantViolation(
format!("{} (got: {:?} vs {:?})", format!($($msg)*), left, right)
));
}
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ invariant!(balance_after == balance_before - amount);

// With custom message
invariant!(balance > 0, "Balance must be positive");
invariant!(a == b, "Expected {} but got {}", a, b);

// Comparison macros (auto-display values on failure)
invariant_eq!(balance_after, balance_before - amount);
invariant_gt!(balance, 0, "Balance must be positive");
```

!!! note "Invariants vs Regular Panics"
Expand Down Expand Up @@ -126,4 +129,12 @@ Notes:
- Without `--exit-code`, invariant failures and program panics are reported but do not force a non-zero process exit.
- Unexpected fuzz-test panics (for example `unwrap()` on `None`) are always treated as runtime errors and fail the run.

## Available Macros

Beyond the basic `invariant!` macro shown above, Trident provides a full family of comparison macros that automatically display values on failure.

See the [Invariant Macros API Reference](../../trident-api/invariants.md) for signatures, failure messages, and examples.

---

For more complex examples and patterns, see the [Trident Examples](../../trident-examples/trident-examples.md) page.
Loading
Loading