diff --git a/content/stellar-contracts/utils/math/wad.mdx b/content/stellar-contracts/utils/math/wad.mdx new file mode 100644 index 0000000..4d1c402 --- /dev/null +++ b/content/stellar-contracts/utils/math/wad.mdx @@ -0,0 +1,381 @@ +--- +title: WAD Fixed-Point Math +description: High-precision decimal arithmetic +--- + +# Overview + +The WAD library provides fixed-point decimal arithmetic for Soroban smart contracts with 18 decimal places of precision. +It's designed specifically for DeFi applications where precise decimal calculations are critical, +such as interest rates, exchange rates, and token pricing. + +It is a fixed-point representation where: +- **1.0 is represented as `1_000_000_000_000_000_000` (10^18)** +- **0.5 is represented as `500_000_000_000_000_000`** +- **123.456 is represented as `123_456_000_000_000_000_000`** + +This allows precise decimal arithmetic using only integer operations, avoiding the pitfalls +of floating-point arithmetic in smart contracts. + +# Why WAD? + +## Problems with Alternatives + +**Native Integers (`i128`, `u64`):** +- No decimal support - `1/2 = 0` instead of `0.5` +- Loss of precision in financial calculations +- Requires manual scaling for each operation + +**Floating-Point (`f64`, `f32`):** +- Non-deterministic behavior across platforms +- Rounding errors that compound in financial calculations +- Security vulnerabilities from precision loss + +## Why WAD is Better + +- **High Precision**: 18 decimals is more than sufficient for financial calculations +- **Deterministic**: Same inputs always produce same outputs +- **Efficient**: Uses native `i128` arithmetic under the hood +- **Battle-Tested**: Used in production by MakerDAO, Uniswap, Aave, and others +- **Ergonomic**: Operator overloading makes code readable: `a + b * c` +- **Type Safe**: NewType pattern prevents mixing scaled and unscaled values + +# Design Decisions + +## 1. NewType Pattern + +We use a NewType `struct Wad(i128)` instead of a type alias: + +```rust +// Type alias +type Wad = i128; + +// NewType +pub struct Wad(i128); +``` + +**Benefits:** +- **Type Safety**: Cannot accidentally mix scaled and unscaled values +- **Operator Overloading**: Can implement `+`, `-`, `*`, `/` with correct semantics +- **Semantic Clarity**: Makes intent explicit in function signatures + +## 2. No `From`/`Into` Traits + +We deliberately **DID NOT** implement `From` or `Into` because it's ambiguous: + +```rust +// What should this mean? +let wad = Wad::from(5); +// Is it 5.0 (scaled to 5 * 10^18)? +// Or 0.000000000000000005 (raw value 5)? +``` + +Instead, we provide explicit constructors: +- `Wad::from_integer(e, 5)` - Creates 5.0 (scaled) +- `Wad::from_raw(5)` - Creates raw value 5 (0.000000000000000005) + +## 3. Truncation vs Rounding + +All operations truncate toward zero rather than rounding: + +**Why truncation?** +- **Predictable**: Same behavior as integer division +- **Conservative**: In financial calculations, truncation is often safer (e.g., don't over-calculate interest) +- **Fast**: No additional logic needed + +## 4. Operator Overloading + +We provide operator overloading (`+`, `-`, `*`, `/`, `-`) for convenience: + +```rust +// Readable arithmetic +let total = price + fee; +let cost = quantity * price; +let ratio = numerator / denominator; +``` + +Operator overloading is supported across WAD and native i128 types where unambiguous: +`WAD * i128`, `i128 * WAD`, `WAD / i128`. + +**Explicit methods are available for safety:** +- `checked_add()`, `checked_sub()`, etc. return `Option` for overflow handling + + + +**Overflow Behavior** + +Just like regular Rust, operator overloading does not include overflow checks: + +- Use `checked_*` methods (`checked_add()`, `checked_sub()`, `checked_mul()`, etc.) when handling user inputs or when overflow is possible. These return `Option` for safe error handling. +- Use operator overloads (`+`, `-`, `*`, `/`) when you want to reduce computational overhead by skipping overflow checks, or when you're confident the operation cannot overflow. + +This design follows Rust's standard library pattern: operators for performance, checked methods for safety. + + + +# How It Works + +## Internal Representation + +```rust +pub struct Wad(i128); // Internal representation +pub const WAD_SCALE: i128 = 1_000_000_000_000_000_000; // 10^18 +``` + +A `Wad` is simply a wrapper around `i128` that interprets the value as having 18 decimal places. + +## Arithmetic Operations + +**Addition/Subtraction:** Direct on internal values +```rust +impl Add for Wad { + fn add(self, rhs: Wad) -> Wad { + Wad(self.0 + rhs.0) + } +} +``` + +**Multiplication:** Scale down by WAD_SCALE +```rust +impl Mul for Wad { + fn mul(self, rhs: Wad) -> Wad { + // (a * b) / 10^18 + Wad((self.0 * rhs.0) / WAD_SCALE) + } +} +``` + +**Division:** Scale up by WAD_SCALE +```rust +impl Div for Wad { + fn div(self, rhs: Wad) -> Wad { + // (a * 10^18) / b + Wad((self.0 * WAD_SCALE) / rhs.0) + } +} +``` + +## Exponentiation + +WAD supports raising a value to an unsigned integer exponent via `pow`. + +- `pow(&e, exponent)` is optimized using exponentiation by squaring (O(log n) multiplications). +- Each multiplication keeps WAD semantics (fixed-point multiplication and truncation toward zero). +- Overflow is reported via Soroban errors. + +In addition to `pow`, WAD also provides `checked_pow`, which returns `None` on overflow. + +```rust +// Compound interest multiplier: (1.05)^10 +let rate = Wad::from_ratio(&e, 105, 100); // 1.05 +let multiplier = rate.pow(&e, 10); +``` + +### Notes on `pow` and Phantom Overflow + +`pow` / `checked_pow` are implemented using exponentiation by squaring and rely +on Soroban fixed-point helpers that can automatically scale intermediate products +to `I256` when needed. + +This avoids **phantom overflow** cases where an intermediate multiplication would +overflow `i128`, but the final scaled result would still fit in `i128`. + + +## Token Conversions + +Different tokens have different decimal places (USDC: 6, XLM: 7, ETH: 18, BTC: 8). WAD handles these conversions: + +```rust +// Convert from USDC (6 decimals) to WAD +let usdc_amount: i128 = 1_500_000; // 1.5 USDC +let wad = Wad::from_token_amount(&e, usdc_amount, 6); +// wad.raw() = 1_500_000_000_000_000_000 (1.5 in WAD) + +// Convert back to USDC +let usdc_back: i128 = wad.to_token_amount(&e, 6); +// usdc_back = 1_500_000 +``` + +# Precision Characteristics + +## Understanding Fixed-Point Precision + +WAD is a fixed-point math library. Like all fixed-point arithmetic systems, +precision loss is inherent and unavoidable. The goal is not to eliminate precision errors +—that's impossible— but to reduce them to a degree so minimal +that they become irrelevant in practical applications. + +WAD achieves this goal exceptionally well. With precision loss in the range of **10^-16**, +the errors are so microscopically small that they have zero practical impact on financial calculations. +To put this in perspective: if you're calculating with millions of dollars, the error would be +measured in quadrillionths of a cent. + +## How Precision Loss Manifests + +Due to truncation in each operation, operation order can produce slightly different results: + +```rust +let a = Wad::from_integer(&e, 1000); +let b = Wad::from_raw(55_000_000_000_000_000); // 0.055 +let c = Wad::from_raw(8_333_333_333_333_333); // ~0.00833 + +let result1 = a * b * c; // Truncates after first multiplication +let result2 = a * (b * c); // Truncates after inner multiplication + +// result1 and result2 may differ by ~315 WAD units +// That's 0.000000000000000315 or (3.15 × 10^-16) +``` + +**Why This Doesn't Matter:** +- Errors are in the **10^-15 to 10^-18** range, far beyond practical significance +- Token precision (6-8 decimals) completely absorbs these errors when converting back +- Real-world financial systems round to 2-8 decimal places; WAD's 18 decimals provide a massive safety margin +- This is orders of magnitude more precise than needed for DeFi applications + +# Usage Examples + +## Basic Arithmetic + +```rust +use soroban_sdk::Env; +use stellar_contract_utils::math::wad::Wad; + +fn calculate_interest(e: &Env, principal: i128, rate_bps: u32) -> i128 { + // Convert principal (assume 6 decimals like USDC) + let principal_wad = Wad::from_token_amount(e, principal, 6); + + // Rate in basis points (e.g., 550 = 5.5%) + let rate_wad = Wad::from_ratio(e, rate_bps as i128, 10_000); + + // Calculate interest + let interest_wad = principal_wad * rate_wad; + + // Convert back to token amount + interest_wad.to_token_amount(e, 6) +} +``` + +## Price Calculations + +```rust +fn calculate_swap_output( + e: &Env, + amount_in: i128, + reserve_in: i128, + reserve_out: i128, +) -> i128 { + // Convert to WAD + let amount_in_wad = Wad::from_token_amount(e, amount_in, 6); + let reserve_in_wad = Wad::from_token_amount(e, reserve_in, 6); + let reserve_out_wad = Wad::from_token_amount(e, reserve_out, 6); + + // Constant product formula: amount_out = (amount_in * reserve_out) / (reserve_in + amount_in) + let numerator = amount_in_wad * reserve_out_wad; + let denominator = reserve_in_wad + amount_in_wad; + let amount_out_wad = numerator / denominator; + + // Convert back + amount_out_wad.to_token_amount(e, 6) +} +``` + +## Compound Interest + +```rust +fn calculate_compound_interest( + e: &Env, + principal: i128, + annual_rate_bps: u32, + days: u32, +) -> i128 { + let principal_wad = Wad::from_token_amount(e, principal, 6); + let rate = Wad::from_ratio(e, annual_rate_bps as i128, 10_000); + let time_fraction = Wad::from_ratio(e, days as i128, 365); + + // Simple interest: principal * rate * time + let interest = principal_wad * rate * time_fraction; + + interest.to_token_amount(e, 6) +} +``` + +## Safe Arithmetic with Overflow Checks + +```rust +fn safe_multiply(e: &Env, a: i128, b: i128) -> Result { + let a_wad = Wad::from_token_amount(e, a, 6); + let b_wad = Wad::from_token_amount(e, b, 6); + + // Use checked variant + let result_wad = a_wad + .checked_mul(e, b_wad) + .ok_or(Error::Overflow)?; + + Ok(result_wad.to_token_amount(e, 6)) +} +``` + +# API Reference + +## Constructors + +| Method | Description | Example | +|--------|-------------|---------| +| `from_integer(e, n)` | Create from whole number | `Wad::from_integer(&e, 5)` → 5.0 | +| `from_ratio(e, num, den)` | Create from fraction | `Wad::from_ratio(&e, 1, 2)` → 0.5 | +| `from_token_amount(e, amount, decimals)` | Create from token amount | `Wad::from_token_amount(&e, 1_500_000, 6)` → 1.5 | +| `from_price(e, price, decimals)` | Alias for `from_token_amount` | `Wad::from_price(&e, 100_000, 6)` → 0.1 | +| `from_raw(raw)` | Create from raw i128 value | `Wad::from_raw(10^18)` → 1.0 | + +## Converters + +| Method | Description | Example | +|--------|-------------|---------| +| `to_integer()` | Convert to whole number (truncates) | `Wad(5.7).to_integer()` → 5 | +| `to_token_amount(e, decimals)` | Convert to token amount | `Wad(1.5).to_token_amount(&e, 6)` → 1_500_000 | +| `raw()` | Get raw i128 value | `Wad(1.0).raw()` → 10^18 | + +## Arithmetic Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `a + b` | Addition | `Wad(1.5) + Wad(2.3)` → 3.8 | +| `a - b` | Subtraction | `Wad(5.0) - Wad(3.0)` → 2.0 | +| `a * b` | Multiplication (WAD × WAD) | `Wad(2.0) * Wad(3.0)` → 6.0 | +| `a / b` | Division (WAD ÷ WAD) | `Wad(6.0) / Wad(2.0)` → 3.0 | +| `a * n` | Multiply WAD by integer | `Wad(2.5) * 3` → 7.5 | +| `n * a` | Multiply integer by WAD | `3 * Wad(2.5)` → 7.5 | +| `a / n` | Divide WAD by integer | `Wad(7.5) / 3` → 2.5 | +| `-a` | Negation | `-Wad(5.0)` → -5.0 | + +## Checked Arithmetic + +| Method | Returns | Description | +|--------|---------|-------------| +| `checked_add(rhs)` | `Option` | Addition with overflow check | +| `checked_sub(rhs)` | `Option` | Subtraction with overflow check | +| `checked_mul(e, rhs)` | `Option` | Multiplication with overflow check (handles phantom overflow internally) | +| `checked_div(rhs)` | `Option` | Division with overflow/zero check | +| `checked_mul_int(n)` | `Option` | Integer multiplication with overflow check | +| `checked_div_int(n)` | `Option` | Integer division with zero check | +| `checked_pow(e, exponent)` | `Option` | Exponentiation with overflow check | + +## Utility Methods + +| Method | Description | +|--------|-------------| +| `abs()` | Absolute value | +| `min(other)` | Minimum of two values | +| `max(other)` | Maximum of two values | +| `pow(e, exponent)` | Raises WAD to an unsigned integer power (panics with Soroban error on overflow) | + +## Error Handling + +WAD uses Soroban's contract error system via `SorobanFixedPointError`: + +```rust +pub enum SorobanFixedPointError { + Overflow = 1500, + DivisionByZero = 1501, +} +``` diff --git a/src/navigation/stellar.json b/src/navigation/stellar.json index 771f521..2548706 100644 --- a/src/navigation/stellar.json +++ b/src/navigation/stellar.json @@ -100,6 +100,17 @@ "name": "Upgradeable", "url": "/stellar-contracts/utils/upgradeable" }, + { + "type": "folder", + "name": "Math", + "children": [ + { + "type": "page", + "name": "WAD", + "url": "/stellar-contracts/utils/math/wad" + } + ] + }, { "type": "folder", "name": "Cryptography",