diff --git a/contracts/reputation/src/fixed_point.rs b/contracts/reputation/src/fixed_point.rs new file mode 100644 index 0000000..8300a30 --- /dev/null +++ b/contracts/reputation/src/fixed_point.rs @@ -0,0 +1,42 @@ +/// Safe fixed-point arithmetic for reputation calculations +/// All division operations are protected against division by zero + +/// Calculate average rating with safe division +/// Returns 0 if review_count is 0 (no division by zero) +pub fn calculate_avg_rating(total_points: i128, review_count: u32) -> i32 { + if review_count == 0 { + return 0; + } + + // Safe division: total_points / review_count + // Returns 0 if division would result in overflow or underflow + let count = review_count as i128; + let avg = total_points / count; + + // Clamp to valid rating range (0-5 in 1000-scale) + avg.clamp(0, 5000) as i32 +} + +/// Safe division for basis point calculations +/// Returns 0 if denominator is 0 +pub fn safe_div_bps(numerator: i128, denominator: i128) -> i32 { + if denominator == 0 { + return 0; + } + + let result = numerator / denominator; + result.clamp(0, 10_000) as i32 +} + +/// Multiply two basis point values with safe division +/// Returns 0 if denominator is 0 +pub fn bps_multiply(a: i32, b: i32, scale: i32) -> i32 { + if scale == 0 { + return 0; + } + + let product = (a as i128) * (b as i128); + let result = product / (scale as i128); + + result.clamp(0, 10_000) as i32 +} diff --git a/contracts/reputation/src/lib.rs b/contracts/reputation/src/lib.rs index c7f11c7..6ed0030 100644 --- a/contracts/reputation/src/lib.rs +++ b/contracts/reputation/src/lib.rs @@ -1,5 +1,6 @@ -#![no_std] +#![no_std] +mod fixed_point; mod profile; mod storage; diff --git a/contracts/reputation/src/test.rs b/contracts/reputation/src/test.rs index ac7faba..5ad6886 100644 --- a/contracts/reputation/src/test.rs +++ b/contracts/reputation/src/test.rs @@ -6,6 +6,33 @@ use crate::{ use soroban_sdk::{Address, Env, String}; use soroban_sdk::testutils::Address as _; +#[test] +fn test_fixed_point_zero_division_protection() { + // Test calculate_avg_rating with zero review count + let avg = crate::fixed_point::calculate_avg_rating(15000, 0); + assert_eq!(avg, 0); // Should return 0 instead of panicking + + // Test safe_div_bps with zero denominator + let result = crate::fixed_point::safe_div_bps(10000, 0); + assert_eq!(result, 0); // Should return 0 instead of panicking + + // Test bps_multiply with zero scale + let result = crate::fixed_point::bps_multiply(5000, 2, 0); + assert_eq!(result, 0); // Should return 0 instead of panicking + + // Test calculate_avg_rating with valid inputs + let avg = crate::fixed_point::calculate_avg_rating(15000, 3); + assert_eq!(avg, 5000); // 15000/3 = 5000 + + // Test safe_div_bps with valid inputs + let result = crate::fixed_point::safe_div_bps(5000, 2); + assert_eq!(result, 2500); // 5000/2 = 2500 + + // Test bps_multiply with valid inputs + let result = crate::fixed_point::bps_multiply(5000, 2, 10000); + assert_eq!(result, 1); // (5000*2)/10000 = 1 +} + #[test] fn test_initialize() { let env = Env::default();