stellar_contract_utils/math/
wad.rs

1use core::{
2    cmp::{Ord, PartialOrd},
3    ops::{Add, Div, Mul, Neg, Sub},
4};
5
6use soroban_sdk::{panic_with_error, Env};
7
8use crate::math::{SorobanFixedPointError, SorobanMulDiv};
9
10/// Fixed-point decimal number with 18 decimal places of precision.
11///
12/// `Wad` represents decimal numbers using a fixed-point representation where
13/// 1.0 is stored as `1_000_000_000_000_000_000` (10^18). This provides precise
14/// decimal arithmetic suitable for financial calculations in smart contracts.
15///
16/// # Truncation
17///
18/// All arithmetic operations truncate toward zero rather than rounding:
19/// - `5 / 2 = 2` (not 2.5 or 3)
20/// - `-5 / 2 = -2` (not -2.5 or -3)
21///
22/// ## Precision
23///
24/// Due to truncation on each multiplication/division, the order of operations
25/// can affect results:
26///
27/// ```ignore
28/// let a = Wad::from_integer(&e, 1000);
29/// let b = Wad::from_raw(55_000_000_000_000_000);  // 0.055
30/// let c = Wad::from_raw(8_333_333_333_333_333);   // ~0.00833
31///
32/// let result1 = a * b * c;      // Truncates after first multiplication
33/// let result2 = a * (b * c);    // Truncates after inner multiplication
34/// // result1 and result2 may differ by ~10^-16 due to different truncation points
35/// ```
36///
37/// **Typical precision loss:** ~10^-15 to 10^-16 in relative terms, which is
38/// negligible when converting to typical token precision (6-8 decimals).
39///
40/// # Examples
41///
42/// ```ignore
43/// use soroban_sdk::Env;
44/// use contract_utils::math::wad::Wad;
45///
46/// let e = Env::default();
47///
48/// // Creating Wad values
49/// let five = Wad::from_integer(&e, 5);           // 5.0
50/// let half = Wad::from_ratio(&e, 1, 2);          // 0.5
51/// let price = Wad::from_token_amount(&e, 1_500_000, 6); // 1.5 (from USDC)
52///
53/// // Arithmetic
54/// let sum = five + half;                          // 5.5
55/// let product = five * half;                      // 2.5
56/// let quotient = five / half;                     // 10.0
57///
58/// // Converting back to token amounts
59/// let usdc_amount = product.to_token_amount(&e, 6); // 2_500_000 (2.5 USDC)
60/// ```
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
62pub struct Wad(i128);
63
64pub const WAD_SCALE: i128 = 1_000_000_000_000_000_000;
65
66// ################## ERRORS ##################
67
68fn pow10(e: &Env, exp: u32) -> i128 {
69    if exp > 38 {
70        panic_with_error!(e, SorobanFixedPointError::Overflow);
71    }
72    10_i128.pow(exp)
73}
74
75impl Wad {
76    /// Creates a Wad from an integer by applying WAD scaling.
77    ///
78    /// Treats the input as a whole number and scales it to WAD precision (18
79    /// decimals).
80    ///
81    /// # Arguments
82    ///
83    /// * `e` - Access to the Soroban environment.
84    /// * `n` - The integer value to convert to WAD representation.
85    ///
86    /// # Errors
87    ///
88    /// * [`SorobanFixedPointError::Overflow`] - When the multiplication
89    ///   overflows i128.
90    ///
91    /// # Examples
92    ///
93    /// ```ignore
94    /// let wad = Wad::from_integer(&e, 5);
95    /// assert_eq!(wad.raw(), 5_000_000_000_000_000_000);
96    /// ```
97    ///
98    /// # Notes
99    ///
100    /// Compare with [`Wad::from_raw`] which does NOT apply WAD scaling.
101    pub fn from_integer(e: &Env, n: i128) -> Self {
102        Wad(n
103            .checked_mul(WAD_SCALE)
104            .unwrap_or_else(|| panic_with_error!(e, SorobanFixedPointError::Overflow)))
105    }
106
107    /// Converts Wad back to an integer by removing WAD scaling.
108    ///
109    /// Truncates toward zero, discarding any fractional part.
110    ///
111    /// # Examples
112    ///
113    /// ```ignore
114    /// let wad = Wad::from_raw(5_000_000_000_000_000_000);
115    /// assert_eq!(wad.to_integer(), 5);
116    /// ```
117    pub fn to_integer(self) -> i128 {
118        self.0 / WAD_SCALE
119    }
120
121    /// Creates a Wad from a ratio (num / den).
122    ///
123    /// # Arguments
124    ///
125    /// * `e` - Access to the Soroban environment.
126    /// * `num` - The numerator of the ratio.
127    /// * `den` - The denominator of the ratio.
128    ///
129    /// # Errors
130    ///
131    /// * [`SorobanFixedPointError::DivisionByZero`] - When `den` is zero.
132    /// * [`SorobanFixedPointError::Overflow`] - When the multiplication
133    ///   overflows i128.
134    ///
135    /// # Examples
136    ///
137    /// ```ignore
138    /// let wad = Wad::from_ratio(&e, 5, 10);
139    /// assert_eq!(wad.raw(), 500_000_000_000_000_000); // 0.5 in WAD
140    /// ```
141    pub fn from_ratio(e: &Env, num: i128, den: i128) -> Self {
142        if den == 0 {
143            panic_with_error!(e, SorobanFixedPointError::DivisionByZero)
144        }
145        num.checked_mul_div(e, &WAD_SCALE, &den)
146            .map(Wad)
147            .unwrap_or_else(|| panic_with_error!(e, SorobanFixedPointError::Overflow))
148    }
149
150    /// Creates a Wad from a token amount with specified decimals.
151    ///
152    /// Converts a token's native representation to WAD (18 decimals).
153    /// Truncates toward zero when scaling down (token_decimals > 18).
154    ///
155    /// # Arguments
156    ///
157    /// * `e` - Access to the Soroban environment.
158    /// * `amount` - The token amount in its smallest unit.
159    /// * `token_decimals` - The number of decimals the token uses.
160    ///
161    /// # Errors
162    ///
163    /// * [`SorobanFixedPointError::Overflow`] - When the scaling multiplication
164    ///   overflows i128.
165    ///
166    /// # Examples
167    ///
168    /// ```ignore
169    /// // USDC has 2 decimals, so 1 USDC = 100 units
170    /// let wad = Wad::from_token_amount(&e, 100, 2);
171    /// assert_eq!(wad.raw(), 1_000_000_000_000_000_000); // 1.0 in WAD
172    /// ```
173    ///
174    /// # Notes
175    ///
176    /// `amount` must be in the token's smallest unit. For example, to represent
177    /// 1 USDC (2 decimals), pass `100`, not `1`.
178    pub fn from_token_amount(e: &Env, amount: i128, token_decimals: u8) -> Self {
179        if token_decimals == 18 {
180            Wad(amount)
181        } else if token_decimals < 18 {
182            let diff = 18u32 - token_decimals as u32;
183            let factor = pow10(e, diff);
184            Wad(amount
185                .checked_mul(factor)
186                .unwrap_or_else(|| panic_with_error!(e, SorobanFixedPointError::Overflow)))
187        } else {
188            let diff = token_decimals as u32 - 18u32;
189            let factor = pow10(e, diff);
190            Wad(amount / factor)
191        }
192    }
193
194    /// Converts Wad to a token amount with specified decimals.
195    ///
196    /// Converts from WAD (18 decimals) back to a token's native representation.
197    /// Truncates toward zero when scaling down (token_decimals < 18).
198    ///
199    /// # Arguments
200    ///
201    /// * `e` - Access to the Soroban environment.
202    /// * `token_decimals` - The number of decimals the target token uses.
203    ///
204    /// # Errors
205    ///
206    /// * [`SorobanFixedPointError::Overflow`] - When the scaling multiplication
207    ///   overflows i128 (occurs when `token_decimals > 18`).
208    ///
209    /// # Examples
210    ///
211    /// ```ignore
212    /// let wad = Wad::from_raw(1_000_000_000_000_000_000); // 1.0 in WAD
213    /// let usdc_amount = wad.to_token_amount(&e, 2);
214    /// assert_eq!(usdc_amount, 100); // 1 USDC = 100 units
215    /// ```
216    pub fn to_token_amount(self, e: &Env, token_decimals: u8) -> i128 {
217        if token_decimals == 18 {
218            self.0
219        } else if token_decimals < 18 {
220            let diff = 18u32 - token_decimals as u32;
221            let factor = pow10(e, diff);
222            self.0 / factor
223        } else {
224            let diff = token_decimals as u32 - 18u32;
225            let factor = pow10(e, diff);
226            self.0
227                .checked_mul(factor)
228                .unwrap_or_else(|| panic_with_error!(e, SorobanFixedPointError::Overflow))
229        }
230    }
231
232    /// Creates a Wad from a price with specified decimals.
233    ///
234    /// This is an alias for [`Wad::from_token_amount`].
235    ///
236    /// # Arguments
237    ///
238    /// * `e` - Access to the Soroban environment.
239    /// * `price_integer` - The price in its smallest unit.
240    /// * `price_decimals` - The number of decimals the price uses.
241    ///
242    /// # Errors
243    ///
244    /// refer to [`Wad::from_token_amount`] errors.
245    pub fn from_price(e: &Env, price_integer: i128, price_decimals: u8) -> Self {
246        Wad::from_token_amount(e, price_integer, price_decimals)
247    }
248
249    /// Returns the raw i128 value without applying WAD scaling.
250    ///
251    /// Returns the internal representation directly.
252    ///
253    /// # Examples
254    ///
255    /// ```ignore
256    /// let wad = Wad::from_integer(5);
257    /// assert_eq!(wad.raw(), 5_000_000_000_000_000_000);
258    /// ```
259    pub fn raw(self) -> i128 {
260        self.0
261    }
262
263    /// Creates a Wad from a raw i128 value without applying WAD scaling.
264    ///
265    /// Interprets the input as the internal representation directly.
266    ///
267    /// # Arguments
268    ///
269    /// * `raw` - The raw internal value.
270    ///
271    /// # Examples
272    ///
273    /// ```ignore
274    /// let wad = Wad::from_raw(5);
275    /// // Represents 0.000000000000000005 in decimal
276    /// assert_eq!(wad.raw(), 5);
277    /// ```
278    ///
279    /// # Notes
280    ///
281    /// Compare with [`Wad::from_integer`] which applies WAD scaling.
282    pub fn from_raw(raw: i128) -> Self {
283        Wad(raw)
284    }
285
286    /// Returns the minimum of two Wad values.
287    ///
288    /// # Arguments
289    ///
290    /// * `other` - The other Wad value to compare.
291    pub fn min(self, other: Self) -> Self {
292        if self <= other {
293            self
294        } else {
295            other
296        }
297    }
298
299    /// Returns the maximum of two Wad values.
300    ///
301    /// # Arguments
302    ///
303    /// * `other` - The other Wad value to compare.
304    pub fn max(self, other: Self) -> Self {
305        if self >= other {
306            self
307        } else {
308            other
309        }
310    }
311
312    // ################## CHECKED ARITHMETIC ##################
313
314    /// Checked addition. Returns `None` on overflow.
315    pub fn checked_add(self, rhs: Wad) -> Option<Wad> {
316        self.0.checked_add(rhs.0).map(Wad)
317    }
318
319    /// Checked subtraction. Returns `None` on overflow.
320    pub fn checked_sub(self, rhs: Wad) -> Option<Wad> {
321        self.0.checked_sub(rhs.0).map(Wad)
322    }
323
324    /// Checked multiplication (Wad * Wad).
325    ///
326    /// Returns `None` on overflow. Handles phantom overflow by scaling to
327    /// `I256` when intermediate multiplication overflows `i128` but the final
328    /// result fits. Result is truncated toward zero after division by
329    /// `WAD_SCALE`.
330    pub fn checked_mul(self, e: &Env, rhs: Wad) -> Option<Wad> {
331        self.0.checked_mul_div(e, &rhs.0, &WAD_SCALE).map(Wad)
332    }
333
334    /// Checked division (Wad / Wad). Returns `None` on overflow or division by
335    /// zero.
336    ///
337    /// Result is truncated toward zero.
338    pub fn checked_div(self, e: &Env, rhs: Wad) -> Option<Wad> {
339        if rhs.0 == 0 {
340            return None;
341        }
342        self.0.checked_mul_div(e, &WAD_SCALE, &rhs.0).map(Wad)
343    }
344
345    /// Checked multiplication by integer. Returns `None` on overflow.
346    pub fn checked_mul_int(self, n: i128) -> Option<Wad> {
347        self.0.checked_mul(n).map(Wad)
348    }
349
350    /// Checked division by integer. Returns `None` on division by zero.
351    pub fn checked_div_int(self, n: i128) -> Option<Wad> {
352        if n == 0 {
353            return None;
354        }
355        Some(Wad(self.0 / n))
356    }
357
358    /// Returns the absolute value of the Wad.
359    ///
360    /// # Examples
361    ///
362    /// ```ignore
363    /// let e = Env::default();
364    /// let negative = Wad::from_integer(&e, -5);
365    /// assert_eq!(negative.abs(), Wad::from_integer(&e, 5));
366    /// ```
367    pub fn abs(self) -> Self {
368        Wad(self.0.abs())
369    }
370
371    /// Raises Wad to an unsigned integer power using exponentiation by
372    /// squaring.
373    ///
374    /// This method is optimized for efficiency, computing the result in O(log
375    /// n) multiplications where n is the exponent. Each multiplication
376    /// maintains fixed-point precision by dividing by WAD_SCALE, with
377    /// truncation toward zero.
378    ///
379    /// # Arguments
380    ///
381    /// * `e` - Access to the Soroban environment for error handling.
382    /// * `exponent` - The unsigned integer exponent (0 to 2^32-1).
383    ///
384    /// # Errors
385    ///
386    /// * [`SorobanFixedPointError::Overflow`] - When intermediate or final
387    ///   result exceeds i128 bounds.
388    ///
389    /// # Examples
390    ///
391    /// ```ignore
392    /// // Compound interest: (1.05)^10
393    /// let rate = Wad::from_ratio(&e, 105, 100);  // 1.05
394    /// let final_multiplier = rate.pow(&e, 10);
395    /// let final_amount = principal * final_multiplier;
396    ///
397    /// // Quadratic bonding curve: price = supply^2
398    /// let supply = Wad::from_integer(&e, 1000);
399    /// let price = supply.pow(&e, 2);
400    /// ```
401    pub fn pow(self, e: &Env, exponent: u32) -> Self {
402        self.checked_pow(e, exponent)
403            .unwrap_or_else(|| panic_with_error!(e, SorobanFixedPointError::Overflow))
404    }
405
406    /// Checked version of [`Wad::pow`].
407    ///
408    /// Returns `None` instead of panicking on overflow. Handles phantom
409    /// overflow transparently by scaling to `I256` when intermediate
410    /// multiplications overflow `i128` but the final result fits.
411    ///
412    /// # Arguments
413    ///
414    /// * `e` - Access to the Soroban environment for i256 operations.
415    /// * `exponent` - The unsigned integer exponent.
416    ///
417    /// # Examples
418    ///
419    /// ```ignore
420    /// let e = Env::default();
421    /// let small = Wad::from_integer(&e, 2);
422    /// assert_eq!(small.checked_pow(&e, 10), Some(Wad::from_integer(&e, 1024)));
423    ///
424    /// let large = Wad::from_integer(&e, i128::MAX / WAD_SCALE);
425    /// assert_eq!(large.checked_pow(&e, 2), None); // Overflows
426    /// ```
427    ///
428    /// # Notes
429    ///
430    /// Phantom overflow is handled internally.
431    pub fn checked_pow(self, e: &Env, mut exponent: u32) -> Option<Self> {
432        // Handle base cases
433        if exponent == 0 {
434            return Some(Wad(WAD_SCALE)); // x^0 = 1
435        }
436
437        if exponent == 1 {
438            return Some(self);
439        }
440
441        if self.0 == 0 {
442            return Some(Wad::from_raw(0)); // 0^n = 0
443        }
444
445        if self.0 == WAD_SCALE {
446            return Some(self); // 1^n = 1
447        }
448
449        // Exponentiation by squaring - processes exponent bit-by-bit
450        let mut base = self;
451        let mut result = Wad(WAD_SCALE); // Start with 1 in WAD
452
453        // Example: x^10, where 10 in binary = 1010₂
454        //
455        // Binary:  1    0    1    0
456        //          ↓    ↓    ↓    ↓
457        // Powers:  x^8  x^4  x^2  x^1
458        //          │    │    │    │
459        // Bit=1?   Y    N    Y    N
460        //          │    │    │    │
461        // Action:  MUL  ---  MUL  ---  (only multiply result when bit=1)
462        //          SQR  SQR  SQR  ---  (always square base for next)
463        //
464        // Result: x^8 * x^2 = x^10
465        //
466        // Note: We use checked_mul_div to handle phantom overflow
467        // (where intermediate multiplication overflows i128 but final result fits).
468        // This automatically scales to i256 when needed and returns None if the
469        // result doesn't fit in i128.
470        while exponent > 0 {
471            if exponent & 1 == 1 {
472                // result = result * base (in fixed-point)
473                let new_result = result.0.checked_mul_div(e, &base.0, &WAD_SCALE)?;
474                result = Wad(new_result);
475            }
476
477            exponent >>= 1;
478            if exponent > 0 {
479                // base = base * base (in fixed-point)
480                let new_base = base.0.checked_mul_div(e, &base.0, &WAD_SCALE)?;
481                base = Wad(new_base);
482            }
483        }
484
485        Some(result)
486    }
487}
488
489// Wad + Wad
490impl Add for Wad {
491    type Output = Wad;
492
493    fn add(self, rhs: Wad) -> Wad {
494        Wad(self.0 + rhs.0)
495    }
496}
497
498// Wad - Wad
499impl Sub for Wad {
500    type Output = Wad;
501
502    fn sub(self, rhs: Wad) -> Wad {
503        Wad(self.0 - rhs.0)
504    }
505}
506
507// Wad * Wad: fixed-point multiplication (a * b) / WAD_SCALE
508// Result is truncated toward zero.
509impl Mul for Wad {
510    type Output = Wad;
511
512    fn mul(self, rhs: Wad) -> Wad {
513        Wad((self.0 * rhs.0) / WAD_SCALE)
514    }
515}
516
517// Wad / Wad: fixed-point division (a * WAD_SCALE) / b
518// Result is truncated toward zero.
519impl Div for Wad {
520    type Output = Wad;
521
522    fn div(self, rhs: Wad) -> Wad {
523        Wad((self.0 * WAD_SCALE) / rhs.0)
524    }
525}
526
527// Negation
528impl Neg for Wad {
529    type Output = Wad;
530
531    fn neg(self) -> Wad {
532        Wad(-self.0)
533    }
534}
535
536// Wad * i128: multiply by integer (no WAD scaling)
537impl Mul<i128> for Wad {
538    type Output = Wad;
539
540    fn mul(self, rhs: i128) -> Wad {
541        Wad(self.0 * rhs)
542    }
543}
544
545// i128 * Wad: multiply by integer (no WAD scaling)
546impl Mul<Wad> for i128 {
547    type Output = Wad;
548
549    fn mul(self, rhs: Wad) -> Wad {
550        Wad(self * rhs.0)
551    }
552}
553
554// Wad / i128: divide by integer (no WAD scaling)
555impl Div<i128> for Wad {
556    type Output = Wad;
557
558    fn div(self, rhs: i128) -> Wad {
559        Wad(self.0 / rhs)
560    }
561}
562
563// ============================================================================
564// Design Decision: Why we DON'T implement From<i128> / Into<i128>
565// ============================================================================
566//
567// ```
568// impl From<i32> for Wad {
569//     fn from(n: i32) -> Self {
570//         // `Wad::from_integer(n)` or `Wad::from_raw(n)`?
571//     }
572// }
573// ```
574// ============================================================================
575//
576// The `From<i128>` trait is intentionally NOT implemented because the
577// conversion semantics are fundamentally ambiguous. There are two equally valid
578// interpretations:
579//
580// 1. Scaled conversion (semantic interpretation): `Wad::from(5)` could mean
581//    "the number 5.0" → calls `from_integer(5)` → internal value:
582//    5_000_000_000_000_000_000
583//
584// 2. Unscaled conversion (raw value interpretation): `Wad::from(5)` could mean
585//    "5 raw units" → calls `from_raw(5)` → internal value: 5 (represents
586//    0.000000000000000005)
587//
588// Both interpretations are valid and useful in different contexts. Without
589// explicit context, it's impossible to determine which one the user intends.
590// This ambiguity can lead to critical bugs in financial calculations.
591//
592// Instead, we require explicit method calls:
593// - Use `Wad::from_integer(n)` when you mean "the number n" (will WAD scale the
594//   input)
595// - Use `Wad::from_raw(n)` when you mean "n raw units" (will NOT WAD scale the
596//   input)
597//
598// This design follows Rust API guidelines: conversions should be obvious and
599// unambiguous. When multiple reasonable interpretations exist, use named
600// constructors instead of trait implementations.