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}