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.