Rates

Interest rate primitives — compounding, discounting, log returns, APR↔APY conversions, and closed-form / iterative yield calculations. Built on top of DeFiMath's exp, ln, expm1, and log1p.

Contract: Rates.sol

Functions

FunctionGasDescription
compoundInterest467Continuous compounding: P · e^(r·t)
presentValue519Discounting: FV · e^(−r·t)
logReturn600ln(currentPrice / previousPrice)
continuousToDiscrete508e^apr − 1 (APR → APY)
discreteToContinuous589ln(1 + apy) (APY → APR)
yieldToMaturity736Zero-coupon YTM (closed form)
internalRateOfReturn17k–49kIRR via Newton-Raphson (scales with cashflow count)

npm install defimath-lib

Conventions

  • Continuous compounding throughout. All rate inputs are interpreted as continuous APRs unless the function name says otherwise.
  • principal, futureValue, priceuint128, 18-decimal fixed-point (1e18 = 1.0).
  • rate, apr, apy — annualized as 18-decimal fixed-point. APR/APY conversions take int256 (signed).
  • timeInterval, timeToMaturityuint32, seconds.
  • internalRateOfReturn takes parallel cashflows[] and times[] arrays plus an initial guess for Newton-Raphson.
  • All functions are internal pure.

Quick example

solidity
import "defimath-lib/contracts/finance/Rates.sol";

// Continuous compounding: how much does principal grow in 1 year at 5%?
uint256 fv = DeFiMathRates.compoundInterest(1_000e18, 0.05e18, 365 days);

// Discount a future cashflow back to today.
uint256 pv = DeFiMathRates.presentValue(fv, 0.05e18, 365 days);

// Convert continuous APR (5%) to effective APY.
int256 apy = DeFiMathRates.continuousToDiscrete(int256(0.05e18));

Important notes

  • compoundInterest and presentValue are exact inverses. Round-tripping a value through both reconstructs the original up to the underlying exp precision (~5e-14).
  • APR ↔ APY both use precision-preserving primitives. continuousToDiscrete uses expm1; discreteToContinuous uses log1p. Safe to call with small rates without precision loss.
  • yieldToMaturity is closed-form and cheap; internalRateOfReturn is iterative. Use YTM for zero-coupon bonds (a single ln). Use IRR for arbitrary cashflow schedules — but expect 17k–49k gas depending on cashflow count, and supply a reasonable guess to stay within the iteration budget.
  • IRR can fail to converge. Pathological cashflow sets (no real IRR, or multiple sign changes) revert with NoConvergenceError. The solver caps at 50 Newton-Raphson iterations.

Every function reverts on out-of-bounds inputs with a named error — see the per-function pages for limits and error specifics.

Testing

Hardhat correctness layer. 66 tests across 7 function groups (compound interest, present value, log return, continuous↔discrete rate conversions, yield to maturity, internal rate of return). Each scalar function is sweep-validated against an inline JavaScript reference; internalRateOfReturn is validated against a JS Newton-Raphson IRR for 4- and 12-cashflow series with strict-equality gas regression gates.

Foundry property-fuzz layer. 15 mathematical properties × 32,000 random runs each = 480,000 random executions per CI run.

CategoryCountWhat they check
Round-trips5PV(CI(P, r, t), r, t) ≈ P, CI(PV(FV, r, t), r, t) ≈ FV, APR↔APY round-trip (d2c(c2d(r)) ≈ r), reverse (c2d(d2c(r)) ≈ r), bond YTM round-trip (PV(F, YTM(P, F, t), t) ≈ P)
Monotonicity4CI ↑ in principal, PV ↑ in futureValue, PV ↓ in rate, continuousToDiscrete ↑ in apr
Identities3CI(P, 0, t) = P, CI(P, r, 0) = P, logReturn(p, p) = 0
Output bounds2CI ≥ principal (for rate ≥ 0), PV ≤ futureValue (for rate ≥ 0)
Symmetries1logReturn(p1, p0) = −logReturn(p0, p1) (anti-symmetric in argument order)

Sources: test/Rates.test.mjs · test/foundry/Rates.t.sol