Unit Scaling
Orbital AMM uses three layers of unit scaling to prevent overflow while maintaining precision. This page explains why each layer exists and provides a conversion cheat sheet.
The Three Layers
| Layer | Unit | Example (1M USDC) |
|---|---|---|
| Raw | Microunits (ASA) | 1,000,000,000,000 (10¹²) |
| Scaled | AMOUNT_SCALE units | 1,000,000,000 (10⁹) |
| Math | PRECISION units | 1,000,000 (10⁶) |
Why Scaling Is Needed
The torus invariant requires computing squares of reserves:
For a pool with 50,000 USDC (50,000 × 10⁶ = 5 × 10¹⁰ microunits):
This exceeds uint64 max (~1.8 × 10¹⁹) by 100×. We need to scale down before squaring.
AMOUNT_SCALE
AMOUNT_SCALE = 1000 divides reserves before math operations:
AMOUNT_SCALE = 1000
# Convert raw to scaled
scaled = raw // AMOUNT_SCALE
# Now squaring 50M tokens:
# (50,000,000,000 / 1000)² = (50,000,000)² = 2.5 × 10¹⁵
# This fits in uint64 ✓After scaling by 1000, the max square is 2.5 × 10¹⁵ — well within uint64 range.
PRECISION
PRECISION = 10⁹ is used for fee growth and other fixed-point calculations:
PRECISION = 1_000_000_000
# Fee growth per unit of liquidity
fee_growth += fee_amount * PRECISION // r_intPRECISION provides 9 decimal places of precision — enough for small fee accruals without overflow.
Conversion Cheat Sheet
# Raw → Scaled
scaled = raw // AMOUNT_SCALE # Divide by 1000
# Scaled → Raw
raw = scaled * AMOUNT_SCALE # Multiply by 1000
# Scaled → Math (PRECISION units)
math = scaled * PRECISION // AMOUNT_SCALE
# Math → Scaled
scaled = math * AMOUNT_SCALE // PRECISION
# Raw → Display (human-readable)
display = raw / (10 ** decimals) # e.g., / 10⁶ for USDC
# Display → Raw
raw = int(display * (10 ** decimals))Example Conversions
For 1M USDC (6 decimals):
| From | To | Formula | Result |
|---|---|---|---|
| Display (1M) | Raw | 1M × 10⁶ | 10¹² |
| Raw | Scaled | 10¹² / 1000 | 10⁹ |
| Scaled | Math | 10⁹ × 10⁹ / 1000 | 10¹⁵ |
| Math | Display | 10¹⁵ / 10⁹ / 10⁶ | 1M |
Overflow Prevention
The contract enforces scaling at every step:
# Before any squaring:
assert x < MAX_SAFE_SQUARE_INPUT
# MAX_SAFE_SQUARE_INPUT = floor(sqrt(2⁶⁴ - 1)) ≈ 4.29 × 10⁹
# After AMOUNT_SCALE, max reserve is ~5 × 10⁷
# Square is ~2.5 × 10¹⁵, well below limit ✓Precision Loss
Integer division introduces rounding errors. The contract uses:
- Truncation —
a // balways rounds down - Tolerance — Invariant checks allow ±1000 error
For typical pool sizes (1M-100M), precision loss is < 0.01%. For very small pools (< 10k), it can reach 1%.
SDK note: The SDK handles all conversions internally. You work with display units (1M USDC), and it converts to raw/scaled/math as needed.