Typed Math is a modern, typed spiritual successor to DS-Math for MakerDAO-style fixed-point units. It keeps DS-Math's unsigned wad/ray half-up formulas while using Solidity user-defined value types, operator overloads, and explicit conversion helpers to make unit mistakes harder to write.
| Type | Scale | Meaning | Type docs |
|---|---|---|---|
Wad |
10**18 |
unsigned user-facing quantity | docs/types/Wad.md |
WadFactor |
10**18 |
unsigned dimensionless multiplier or ratio | docs/types/WadFactor.md |
Ray |
10**27 |
unsigned rate, index, or price factor | docs/types/Ray.md |
Rad |
10**45 |
unsigned internal accounting quantity | docs/types/Rad.md |
Same scale does not imply same dimension. Wad is an amount; WadFactor is a
multiplier such as a fee, penalty, percentage, auction parameter, or ratio.
Import the facade. It exposes the UDVTs, constants, errors, TypedMath
namespace, global operators, and member-style helpers:
import {Rad, Ray, TypedMath, Wad, WadFactor} from "typed-math/src/TypedMath.sol";TypedMath.sol is intentionally the package's core catch-all export facade.
The implementation is split internally by type, but consumers should import
from the facade so operator bindings, member helpers, constants, errors, and
namespaced functions stay available from one stable path.
The source is split by type:
| Source file | Responsibility |
|---|---|
src/TypedMath.sol |
canonical facade and namespaced TypedMath helpers |
src/TypedMathDelta.sol |
opt-in signed delta facade |
src/internal/TypedMathTypes.sol |
value types, constants, errors, global using bindings |
src/internal/TypedMathOperators.sol |
free-function operator shims required by Solidity |
src/internal/WadMath.sol |
Wad quantity helpers |
src/internal/WadFactorMath.sol |
dimensionless wad-scale factor helpers |
src/internal/RayMath.sol |
ray helpers |
src/internal/RadMath.sol |
rad and rad-producing cross-unit helpers |
src/internal/TypedMathCore.sol |
shared integer division helpers |
src/internal/*Delta*.sol |
signed delta types and helpers |
Wrap raw scaled integers at system boundaries. Keep business logic typed:
Wad amount = Wad.wrap(2e18);
WadFactor fee = TypedMath.bpsToWadFactor(50); // 0.50%
Wad charged = amount.wmul(fee); // raw 0.01e18
uint256 raw = Wad.unwrap(charged);Same-unit addition, subtraction, and comparisons use Solidity operators:
Wad total = Wad.wrap(1e18) + Wad.wrap(2e18);
Wad diff = Wad.wrap(3e18) - Wad.wrap(1e18);
bool ok = Wad.wrap(1e18) < Wad.wrap(2e18);Namespaced and member-style helpers are both available:
Wad a = TypedMath.wmul(Wad.wrap(2e18), TypedMath.toWadFactor(3));
Wad b = Wad.wrap(2e18).wmul(TypedMath.toWadFactor(3));
WadFactor ratio = Wad.wrap(150e18).wdiv(Wad.wrap(100e18));
Ray compounded = Ray.wrap(1.01e27).rmul(Ray.wrap(1.02e27));
Rad debt = Wad.wrap(100e18).mul(Ray.wrap(1.05e27));wdiv(Wad, Wad) forms a WadFactor ratio between quantities, while
wdiv(Wad, WadFactor) divides a quantity by a factor and returns a Wad.
They are intentionally different dimensions even though both use wad-scale
arithmetic. If the denominator raw values are the same, the numeric quotient is
the same; the distinction is the business meaning of the intermediate value.
Use explicit intermediate variables when that intent matters.
Exact conversions use to* names, truncating conversions use trunc*, and
raw amount/factor reinterpretation uses as*:
Wad exact = Ray.wrap(2e27).toWad();
Wad truncated = Ray.wrap(2e27 + 1).truncWad();
WadFactor factor = Wad.wrap(1e18).asWadFactor();
Wad amount = factor.asWad();The core API is unsigned. Vat-style signed changes are exposed through the separate delta facade:
import {Rad, Ray, Wad} from "typed-math/src/TypedMath.sol";
import {RadDelta, RayDelta, TypedMathDelta, WadDelta, WadDeltaMath} from "typed-math/src/TypedMathDelta.sol";
using WadDeltaMath for Wad;
Wad ink = Wad.wrap(5e18).addDelta(WadDelta.wrap(-1e18));
RadDelta dtab = TypedMathDelta.mul(Ray.wrap(2e27), WadDelta.wrap(3e18));WadDelta, RayDelta, and RadDelta are signed changes applied to unsigned
state. They are not full signed fixed-point quantities and are not DS-Math
semantics.
Typed Math uses three explicit rounding families:
| Name | Meaning |
|---|---|
wmul, wdiv, rmul, rdiv |
DS-Math-compatible half-up formulas |
*Down, / operators for WadFactor and Ray |
floor/truncation toward zero |
*Up division helpers |
ceil/toward positive infinity |
For this unsigned-only API, Down is equivalent to floor/truncation toward
zero, and Up is equivalent to ceil/toward positive infinity.
wdivDownMod and rdivDownMod return raw uint256 residues of the scaled
numerator. They pair with wdivDown, rdivDown, or /, not with half-up
wdiv or rdiv, and the residue is not a normalized fixed-point value.
- docs/SEMANTICS.md: normative behavior map.
- docs/OPERATIONS.md: consolidated operation matrix.
- docs/types/Wad.md: wad quantity operations.
- docs/types/WadFactor.md: wad-scale factor operations.
- docs/types/Ray.md: ray operations.
- docs/types/Rad.md: rad accounting operations.
- docs/types/Delta.md: signed delta extension.
- docs/AUDIT.md: audit checklist, fuzzing, and Halmos workflow.
Run the test suite:
forge test -vvvRun the audit-oriented checks:
forge test --fuzz-runs 10000
TYPED_MATH_ORACLE_SEED=typed-math-oracle TYPED_MATH_ORACLE_CASES=64 forge test --match-contract TypedMathOracleTest -vvv
halmos
halmos --config halmos.strict.tomlFormat Solidity:
forge fmt