From 12b9cf7fc778017d0990d1e8f9f8a84f06c66ee0 Mon Sep 17 00:00:00 2001 From: AvatarMiiDe Date: Thu, 1 Oct 2026 09:18:36 +0100 Subject: [PATCH 1/4] test(math): add boundary and recovery test coverage Expands test_math.rs with comprehensive coverage for all arithmetic helpers. Includes success, rejection, division-by-zero boundary panics, max-limit bounds, and graceful saturation recovery (sat_mul_bps) to ensure math behaves deterministically and preserves safety invariants. --- contracts/credence_bond/src/test_math.rs | 96 ++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/contracts/credence_bond/src/test_math.rs b/contracts/credence_bond/src/test_math.rs index 7d1c47b9c..3085a987c 100644 --- a/contracts/credence_bond/src/test_math.rs +++ b/contracts/credence_bond/src/test_math.rs @@ -32,3 +32,99 @@ fn test_mul_overflow_panics() { fn test_mul_u64_overflow_panics() { let _ = math::mul_u64(u64::MAX, 2, "attestation weight overflow"); } + +#[test] +fn test_add_basic() { + assert_eq!(math::add_i128(100, 200, "add fail"), 300); +} + +#[test] +#[should_panic(expected = "add overflow")] +fn test_add_overflow_panics() { + let _ = math::add_i128(i128::MAX, 1, "add overflow"); +} + +#[test] +fn test_sub_basic() { + assert_eq!(math::sub_i128(500, 200, "sub fail"), 300); +} + +#[test] +fn test_mul_basic() { + assert_eq!(math::mul_i128(50, 2, "mul fail"), 100); +} + +#[test] +fn test_mul_u64_basic() { + assert_eq!(math::mul_u64(50, 2, "mul fail"), 100); +} + +#[test] +fn test_div_basic() { + assert_eq!(math::div_i128(100, 3, "div fail"), 33); +} + +#[test] +#[should_panic(expected = "divide by zero")] +fn test_div_by_zero_panics() { + let _ = math::div_i128(100, 0, "divide by zero"); +} + +#[test] +fn test_ceil_div_basic() { + assert_eq!(math::ceil_div_i128(100, 3, "ceil div fail"), 34); + assert_eq!(math::ceil_div_i128(100, 4, "ceil div fail"), 25); +} + +#[test] +#[should_panic(expected = "divide by zero")] +fn test_ceil_div_by_zero_panics() { + let _ = math::ceil_div_i128(100, 0, "divide by zero"); +} + +#[test] +fn test_sat_mul_bps_basic() { + assert_eq!(math::sat_mul_bps(10_000, 5_000), 5_000); // 50% of 10000 +} + +#[test] +fn test_sat_mul_bps_saturation() { + // Should saturate to i128::MAX instead of panicking, ensuring recovery and safety at limits + assert_eq!(math::sat_mul_bps(i128::MAX, 20_000), i128::MAX); +} + +#[test] +fn test_split_bps_basic() { + let (part, remainder) = math::split_bps(1000, 2000, "mul fail", "div fail", "sub fail"); + assert_eq!(part, 200); + assert_eq!(remainder, 800); +} + +#[test] +fn test_split_bps_boundaries() { + // 0% + let (part_zero, rem_zero) = math::split_bps(1000, 0, "mul", "div", "sub"); + assert_eq!(part_zero, 0); + assert_eq!(rem_zero, 1000); + + // 100% + let (part_full, rem_full) = math::split_bps(1000, 10_000, "mul", "div", "sub"); + assert_eq!(part_full, 1000); + assert_eq!(rem_full, 0); +} + +#[test] +fn test_bps_u64_basic() { + assert_eq!(math::bps_u64(10_000, 100, "mul fail", "div fail"), 100); +} + +#[test] +#[should_panic(expected = "mul u64 overflow")] +fn test_bps_u64_overflow_panics() { + let _ = math::bps_u64(u64::MAX, 10_000, "mul u64 overflow", "div fail"); +} + +#[test] +fn test_bps_denominator() { + assert_eq!(math::BPS_DENOMINATOR, 10_000); +} From de4ec0b225055965a1e40c24731c5c3eb9db596f Mon Sep 17 00:00:00 2001 From: AvatarMiiDe Date: Thu, 1 Oct 2026 09:24:53 +0100 Subject: [PATCH 2/4] test: add boundary and recovery test coverage for math.rs --- contracts/credence_bond/src/test_math.rs | 182 +++++++++++++++++++++-- 1 file changed, 166 insertions(+), 16 deletions(-) diff --git a/contracts/credence_bond/src/test_math.rs b/contracts/credence_bond/src/test_math.rs index 3085a987c..900be08ff 100644 --- a/contracts/credence_bond/src/test_math.rs +++ b/contracts/credence_bond/src/test_math.rs @@ -1,7 +1,14 @@ //! Tests for overflow-safe arithmetic helpers. +//! +//! This module verifies the deterministic behavior, boundary handling, and failure +//! recovery invariants of the arithmetic operations re-exported in `math.rs`. +//! All operations must handle valid, invalid, and boundary-case inputs without +//! producing unsafe or inconsistent results. use crate::math; +// --- bps --- + #[test] fn test_bps_basic() { let fee = math::bps(10_000_i128, 100_u32, "mul", "div"); @@ -16,23 +23,38 @@ fn test_bps_overflow_panics() { } #[test] -#[should_panic(expected = "sub underflow")] -fn test_sub_underflow_panics() { - let _ = math::sub_i128(i128::MIN, 1, "sub underflow"); +fn test_bps_boundaries() { + // 0 amount + assert_eq!(math::bps(0, 10_000, "mul", "div"), 0); + // 0 bps + assert_eq!(math::bps(10_000, 0, "mul", "div"), 0); + // Negative amount + assert_eq!(math::bps(-10_000, 100, "mul", "div"), -100); } +// --- bps_u64 --- + #[test] -#[should_panic(expected = "mul overflow")] -fn test_mul_overflow_panics() { - let _ = math::mul_i128(i128::MAX, 2, "mul overflow"); +fn test_bps_u64_basic() { + assert_eq!(math::bps_u64(10_000, 100, "mul fail", "div fail"), 100); } #[test] -#[should_panic(expected = "attestation weight overflow")] -fn test_mul_u64_overflow_panics() { - let _ = math::mul_u64(u64::MAX, 2, "attestation weight overflow"); +#[should_panic(expected = "mul u64 overflow")] +fn test_bps_u64_overflow_panics() { + let _ = math::bps_u64(u64::MAX, 10_000, "mul u64 overflow", "div fail"); } +#[test] +fn test_bps_u64_boundaries() { + // 0 amount + assert_eq!(math::bps_u64(0, 10_000, "mul", "div"), 0); + // 0 bps + assert_eq!(math::bps_u64(10_000, 0, "mul", "div"), 0); +} + +// --- add_i128 --- + #[test] fn test_add_basic() { assert_eq!(math::add_i128(100, 200, "add fail"), 300); @@ -44,24 +66,102 @@ fn test_add_overflow_panics() { let _ = math::add_i128(i128::MAX, 1, "add overflow"); } +#[test] +#[should_panic(expected = "add underflow")] +fn test_add_underflow_panics() { + let _ = math::add_i128(i128::MIN, -1, "add underflow"); +} + +#[test] +fn test_add_boundaries() { + assert_eq!(math::add_i128(i128::MAX, 0, "add fail"), i128::MAX); + assert_eq!(math::add_i128(i128::MIN, 0, "add fail"), i128::MIN); + assert_eq!(math::add_i128(i128::MAX, -1, "add fail"), i128::MAX - 1); + assert_eq!(math::add_i128(i128::MIN, 1, "add fail"), i128::MIN + 1); +} + +// --- sub_i128 --- + #[test] fn test_sub_basic() { assert_eq!(math::sub_i128(500, 200, "sub fail"), 300); } +#[test] +#[should_panic(expected = "sub underflow")] +fn test_sub_underflow_panics() { + let _ = math::sub_i128(i128::MIN, 1, "sub underflow"); +} + +#[test] +#[should_panic(expected = "sub overflow")] +fn test_sub_overflow_panics() { + let _ = math::sub_i128(i128::MAX, -1, "sub overflow"); +} + +#[test] +fn test_sub_boundaries() { + assert_eq!(math::sub_i128(i128::MAX, 0, "sub fail"), i128::MAX); + assert_eq!(math::sub_i128(i128::MIN, 0, "sub fail"), i128::MIN); + assert_eq!(math::sub_i128(i128::MAX, 1, "sub fail"), i128::MAX - 1); + assert_eq!(math::sub_i128(i128::MIN, -1, "sub fail"), i128::MIN + 1); + assert_eq!(math::sub_i128(0, 0, "sub fail"), 0); +} + +// --- mul_i128 --- + #[test] fn test_mul_basic() { assert_eq!(math::mul_i128(50, 2, "mul fail"), 100); } +#[test] +#[should_panic(expected = "mul overflow")] +fn test_mul_overflow_panics() { + let _ = math::mul_i128(i128::MAX, 2, "mul overflow"); +} + +#[test] +#[should_panic(expected = "mul underflow")] +fn test_mul_underflow_panics() { + let _ = math::mul_i128(i128::MAX, -2, "mul underflow"); +} + +#[test] +fn test_mul_boundaries() { + assert_eq!(math::mul_i128(i128::MAX, 1, "mul fail"), i128::MAX); + assert_eq!(math::mul_i128(i128::MIN, 1, "mul fail"), i128::MIN); + assert_eq!(math::mul_i128(i128::MAX, 0, "mul fail"), 0); + assert_eq!(math::mul_i128(i128::MIN, 0, "mul fail"), 0); + assert_eq!(math::mul_i128(-1, -1, "mul fail"), 1); +} + +// --- mul_u64 --- + #[test] fn test_mul_u64_basic() { assert_eq!(math::mul_u64(50, 2, "mul fail"), 100); } +#[test] +#[should_panic(expected = "attestation weight overflow")] +fn test_mul_u64_overflow_panics() { + let _ = math::mul_u64(u64::MAX, 2, "attestation weight overflow"); +} + +#[test] +fn test_mul_u64_boundaries() { + assert_eq!(math::mul_u64(u64::MAX, 1, "mul fail"), u64::MAX); + assert_eq!(math::mul_u64(u64::MAX, 0, "mul fail"), 0); + assert_eq!(math::mul_u64(0, u64::MAX, "mul fail"), 0); +} + +// --- div_i128 --- + #[test] fn test_div_basic() { assert_eq!(math::div_i128(100, 3, "div fail"), 33); + assert_eq!(math::div_i128(-100, 3, "div fail"), -33); } #[test] @@ -70,10 +170,27 @@ fn test_div_by_zero_panics() { let _ = math::div_i128(100, 0, "divide by zero"); } +#[test] +#[should_panic(expected = "div overflow")] +fn test_div_overflow_panics() { + // i128::MIN / -1 overflows. + let _ = math::div_i128(i128::MIN, -1, "div overflow"); +} + +#[test] +fn test_div_boundaries() { + assert_eq!(math::div_i128(0, 100, "div fail"), 0); + assert_eq!(math::div_i128(i128::MAX, 1, "div fail"), i128::MAX); + assert_eq!(math::div_i128(i128::MIN, 1, "div fail"), i128::MIN); +} + +// --- ceil_div_i128 --- + #[test] fn test_ceil_div_basic() { assert_eq!(math::ceil_div_i128(100, 3, "ceil div fail"), 34); assert_eq!(math::ceil_div_i128(100, 4, "ceil div fail"), 25); + assert_eq!(math::ceil_div_i128(0, 5, "ceil div fail"), 0); } #[test] @@ -82,17 +199,43 @@ fn test_ceil_div_by_zero_panics() { let _ = math::ceil_div_i128(100, 0, "divide by zero"); } +#[test] +#[should_panic(expected = "ceil div overflow")] +fn test_ceil_div_overflow_panics() { + // i128::MAX + (b - 1) overflows when b > 1 + let _ = math::ceil_div_i128(i128::MAX, 2, "ceil div overflow"); +} + +#[test] +fn test_ceil_div_boundaries() { + assert_eq!(math::ceil_div_i128(i128::MAX, 1, "ceil div fail"), i128::MAX); + assert_eq!(math::ceil_div_i128(0, 1, "ceil div fail"), 0); +} + +// --- sat_mul_bps --- + #[test] fn test_sat_mul_bps_basic() { assert_eq!(math::sat_mul_bps(10_000, 5_000), 5_000); // 50% of 10000 + assert_eq!(math::sat_mul_bps(-10_000, 5_000), -5_000); // 50% of -10000 } #[test] fn test_sat_mul_bps_saturation() { // Should saturate to i128::MAX instead of panicking, ensuring recovery and safety at limits assert_eq!(math::sat_mul_bps(i128::MAX, 20_000), i128::MAX); + // Saturates to i128::MIN + assert_eq!(math::sat_mul_bps(i128::MIN, 20_000), i128::MIN); +} + +#[test] +fn test_sat_mul_bps_boundaries() { + assert_eq!(math::sat_mul_bps(0, 20_000), 0); + assert_eq!(math::sat_mul_bps(10_000, 0), 0); } +// --- split_bps --- + #[test] fn test_split_bps_basic() { let (part, remainder) = math::split_bps(1000, 2000, "mul fail", "div fail", "sub fail"); @@ -111,19 +254,26 @@ fn test_split_bps_boundaries() { let (part_full, rem_full) = math::split_bps(1000, 10_000, "mul", "div", "sub"); assert_eq!(part_full, 1000); assert_eq!(rem_full, 0); -} -#[test] -fn test_bps_u64_basic() { - assert_eq!(math::bps_u64(10_000, 100, "mul fail", "div fail"), 100); + // Amount is 0 + let (part_zero_amt, rem_zero_amt) = math::split_bps(0, 5_000, "mul", "div", "sub"); + assert_eq!(part_zero_amt, 0); + assert_eq!(rem_zero_amt, 0); + + // Negative amount + let (part_neg, rem_neg) = math::split_bps(-1000, 2000, "mul", "div", "sub"); + assert_eq!(part_neg, -200); + assert_eq!(rem_neg, -800); } #[test] -#[should_panic(expected = "mul u64 overflow")] -fn test_bps_u64_overflow_panics() { - let _ = math::bps_u64(u64::MAX, 10_000, "mul u64 overflow", "div fail"); +#[should_panic(expected = "mul fail")] +fn test_split_bps_overflow_panics() { + let _ = math::split_bps(i128::MAX, 10_000, "mul fail", "div fail", "sub fail"); } +// --- constants --- + #[test] fn test_bps_denominator() { assert_eq!(math::BPS_DENOMINATOR, 10_000); From b27820fc4b62f025287e4c9a7cea50102c6a92a8 Mon Sep 17 00:00:00 2001 From: AvatarMiiDe Date: Fri, 2 Oct 2026 06:40:57 +0100 Subject: [PATCH 3/4] Fix math boundary tests by adding missing u64::MAX boundary --- contracts/credence_bond/src/test_math.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/credence_bond/src/test_math.rs b/contracts/credence_bond/src/test_math.rs index 900be08ff..71db4ce66 100644 --- a/contracts/credence_bond/src/test_math.rs +++ b/contracts/credence_bond/src/test_math.rs @@ -51,6 +51,8 @@ fn test_bps_u64_boundaries() { assert_eq!(math::bps_u64(0, 10_000, "mul", "div"), 0); // 0 bps assert_eq!(math::bps_u64(10_000, 0, "mul", "div"), 0); + // Max amount with 0 bps + assert_eq!(math::bps_u64(u64::MAX, 0, "mul", "div"), 0); } // --- add_i128 --- From 831a49098b9100a35fadaed17bc0a15c85b59fae Mon Sep 17 00:00:00 2001 From: AvatarMiiDe Date: Fri, 2 Oct 2026 06:42:58 +0100 Subject: [PATCH 4/4] test: add unit tests for overflow-safe arithmetic helpers --- contracts/credence_bond/src/test_math.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/credence_bond/src/test_math.rs b/contracts/credence_bond/src/test_math.rs index 900be08ff..71db4ce66 100644 --- a/contracts/credence_bond/src/test_math.rs +++ b/contracts/credence_bond/src/test_math.rs @@ -51,6 +51,8 @@ fn test_bps_u64_boundaries() { assert_eq!(math::bps_u64(0, 10_000, "mul", "div"), 0); // 0 bps assert_eq!(math::bps_u64(10_000, 0, "mul", "div"), 0); + // Max amount with 0 bps + assert_eq!(math::bps_u64(u64::MAX, 0, "mul", "div"), 0); } // --- add_i128 ---