stellar_contract_utils/math/
mod.rs

1//! # Fixed-Point Math Library
2//!
3//! Provides utilities for precise fixed-point arithmetic operations in Soroban
4//! smart contracts.
5//!
6//! This module implements fixed-point multiplication and division with phantom
7//! overflow handling, ensuring accurate calculations even when intermediate
8//! results temporarily exceed the native integer type bounds.
9//!
10//! ## Design Overview
11//!
12//! The library is built around the [`SorobanFixedPoint`] trait, which provides
13//! both panicking and checked variants of fixed-point operations:
14//!
15//! - **Panicking Variants** (`fixed_mul_floor`, `fixed_mul_ceil`): Panic on
16//!   errors with specific error types ([`SorobanFixedPointError`]).
17//! - **Checked Variants** (`checked_fixed_mul_floor`,
18//!   `checked_fixed_mul_ceil`): Return `None` on errors for graceful error
19//!   handling.
20//!
21//! ### Phantom Overflow Handling
22//!
23//! A key feature is automatic phantom overflow handling. When multiplying two
24//! `i128` values would overflow, the implementation automatically scales up to
25//! `I256` for the intermediate calculation, then scales back down if the final
26//! result fits in `i128`.
27//!
28//! ## Structure
29//!
30//! The module includes:
31//!
32//! - [`SorobanFixedPoint`]: Core trait implemented for `i128` and `I256`.
33//! - [`wad`]: Fixed-point decimal number type with 18 decimal places.
34//! - [`muldiv`] and [`checked_muldiv`]: Public API functions for common
35//!   operations.
36//! - [`Rounding`]: Enum to specify rounding direction (floor or ceil).
37//!
38//! ## Notes
39//!
40//! Based on the Soroban fixed-point mathematics library.
41//! Original implementation: <https://github.com/script3/soroban-fixed-point-math>
42
43mod i128_fixed_point;
44mod i256_fixed_point;
45pub mod wad;
46pub use i128_fixed_point::{checked_mul_div_i128, mul_div_i128};
47pub use i256_fixed_point::{checked_mul_div_i256, mul_div_i256};
48
49#[cfg(test)]
50mod test;
51
52use soroban_sdk::{contracterror, contracttype, Env};
53
54/// Trait for computing mul_div fixed-point calculations with Soroban host
55/// objects.
56///
57/// Provides both panicking and checked variants for floor and ceiling division
58/// operations. Implementations automatically handle phantom overflow by scaling
59/// to larger integer types when necessary.
60pub trait SorobanMulDiv: Sized {
61    /// Calculates floor(x * y / denominator).
62    ///
63    /// # Arguments
64    ///
65    /// * `env` - Access to the Soroban environment.
66    /// * `y` - The multiplicand.
67    /// * `denominator` - The divisor.
68    ///
69    /// # Errors
70    ///
71    /// * [`SorobanFixedPointError::DivisionByZero`] - When `denominator` is
72    ///   zero.
73    /// * [`SorobanFixedPointError::Overflow`] - When a phantom overflow occurs
74    ///   or the result does not fit in `Self`.
75    fn mul_div_floor(&self, e: &Env, y: &Self, denominator: &Self) -> Self;
76
77    /// Calculates ceil(x * y / denominator).
78    ///
79    /// # Arguments
80    ///
81    /// * `env` - Access to the Soroban environment.
82    /// * `y` - The multiplicand.
83    /// * `denominator` - The divisor.
84    ///
85    /// # Errors
86    ///
87    /// * [`SorobanFixedPointError::DivisionByZero`] - When `denominator` is
88    ///   zero.
89    /// * [`SorobanFixedPointError::Overflow`] - When a phantom overflow occurs
90    ///   or the result does not fit in `Self`.
91    fn mul_div_ceil(&self, e: &Env, y: &Self, denominator: &Self) -> Self;
92
93    /// Calculates (x * y / denominator).
94    ///
95    /// # Arguments
96    ///
97    /// * `env` - Access to the Soroban environment.
98    /// * `y` - The multiplicand.
99    /// * `denominator` - The divisor.
100    ///
101    /// # Errors
102    ///
103    /// * [`SorobanFixedPointError::DivisionByZero`] - When `denominator` is
104    ///   zero.
105    /// * [`SorobanFixedPointError::Overflow`] - When a phantom overflow occurs
106    ///   or the result does not fit in `Self`.
107    fn mul_div(&self, e: &Env, y: &Self, denominator: &Self) -> Self;
108
109    /// Checked version of floor(x * y / denominator).
110    ///
111    /// Returns `None` if the result overflows or if `denominator` is zero.
112    ///
113    /// # Arguments
114    ///
115    /// * `env` - Access to the Soroban environment.
116    /// * `y` - The multiplicand.
117    /// * `denominator` - The divisor.
118    fn checked_mul_div_floor(&self, e: &Env, y: &Self, denominator: &Self) -> Option<Self>;
119
120    /// Checked version of ceil(x * y / denominator).
121    ///
122    /// Returns `None` if the result overflows or if `denominator` is zero.
123    ///
124    /// # Arguments
125    ///
126    /// * `env` - Access to the Soroban environment.
127    /// * `y` - The multiplicand.
128    /// * `denominator` - The divisor.
129    fn checked_mul_div_ceil(&self, e: &Env, y: &Self, denominator: &Self) -> Option<Self>;
130
131    /// Checked version of (x * y / denominator).
132    ///
133    /// Returns `None` if the result overflows or if `denominator` is zero.
134    ///
135    /// # Arguments
136    ///
137    /// * `env` - Access to the Soroban environment.
138    /// * `y` - The multiplicand.
139    /// * `denominator` - The divisor.
140    fn checked_mul_div(&self, e: &Env, y: &Self, denominator: &Self) -> Option<Self>;
141}
142
143// ################## ERRORS ##################
144
145#[contracterror]
146#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
147#[repr(u32)]
148pub enum SorobanFixedPointError {
149    /// Arithmetic overflow occurred
150    Overflow = 1500,
151    /// Division by zero
152    DivisionByZero = 1501,
153}
154
155/// Rounding direction for division operations
156#[contracttype]
157pub enum Rounding {
158    /// Round toward negative infinity (down)
159    Floor,
160    /// Round toward positive infinity (up)
161    Ceil,
162    /// Round toward zero (truncation)
163    Truncate,
164}