putOptionPrice

Options

Computes the Black-Scholes price of a European put option in 18-decimal fixed-point, at ~2,739 gas.

Gas

2,739

Max abs. error

5.4e-12

Signature

solidity
function putOptionPrice(
    uint128 spot,
    uint128 strike,
    uint32  timeToExp,
    uint64  volatility,
    uint64  rate
) internal pure returns (uint256 price)

Parameters

NameTypeDescription
spotuint128Current spot price, 18-decimal fixed-point. Must satisfy MIN_SPOT < spot < MAX_SPOT (1e-6 < spot < 1e15).
strikeuint128Strike price, 18-decimal fixed-point. Must lie within [spot/5, spot·5] — the no-arbitrage band the function is precision-tuned for.
timeToExpuint32Time to expiration in seconds. Must satisfy timeToExp < MAX_EXPIRATION (63,072,000 s ≈ 2 years). timeToExp == 0 is allowed (handled as expired).
volatilityuint64Annualized implied volatility, 18-decimal fixed-point (e.g. 60% → 6e17).
rateuint64Annualized risk-free rate, 18-decimal fixed-point. Must satisfy rate < MAX_RATE (4e18 = 400%).

Returns

NameTypeDescription
priceuint256Put option price in 18-decimal fixed-point. Always ≥ 0.

Bounds

BoundValue
MIN_SPOT1e-6 smallest allowed spot price (1e12)
MAX_SPOT1e15 largest allowed spot price (1e33)
MAX_STSP_RATIO5× (strike must lie within [spot/5, spot·5])
MAX_EXPIRATION2 years (63,072,000 seconds)
MAX_RATE400% annual (4e18)

Behavior

  • Validates all five inputs against module-wide constants and reverts with a typed error on any violation.
  • Volatility has no explicit revert — it's bounded only by its uint64 type (max ≈ 1.84e19, i.e. ~1840% annualized). Practical inputs stay well below that ceiling; the MIN_VOL_IV / MAX_VOL_IV constants in the source apply only to the impliedVolatility solver, not the pricer.
  • Fast-path on expiration: when timeToExp == 0, returns intrinsic value max(strike − spot, 0) without running the pricer.
  • Composes four DeFiMath primitives — ln, sqrtTime (specialized sqrt for years), expPositive (rate is non-negative by validation), and stdNormCDF — each independently gas-tuned and validated.
  • Pure internal function; no external calls, no storage. Inlined into the caller's bytecode at compile time.
  • Symmetric counterpart: callOptionPrice uses identical input validation and the same d₁/d₂ machinery — substituting Φ(d₁)/Φ(d₂) for the put's Φ(−d₁)/Φ(−d₂).

How it works

putOptionPrice implements the closed-form Black-Scholes formula for a European put:

P=KerTΦ(d2)SΦ(d1)P = K \, e^{-rT} \cdot \Phi(-d_2) - S \cdot \Phi(-d_1)
d1=ln(S/K)+(r+σ22)TσT,d2=d1σTd_1 = \frac{\ln(S/K) + \left(r + \tfrac{\sigma^2}{2}\right) T}{\sigma \sqrt{T}}, \qquad d_2 = d_1 - \sigma \sqrt{T}

The intermediate values d₁ and d₂ are computed exactly as in callOptionPrice; the put's only structural difference is evaluating Φ at −d₁/−d₂ instead of d₁/d₂, then reversing the order of the two terms in the final difference. Put-call parity guarantees the two prices stay consistent with each other to within the combined rounding of the underlying primitives.

Every transcendental maps to a DeFiMath primitive: σ·√T uses DeFiMath.sqrtTime (specialized for time-in-years inputs), ln(spot/strike) uses DeFiMath.ln, the discount factor e^(−rT) is computed as 1 / DeFiMath.expPositive(rT) (positive-input branch, since the input bounds guarantee rT ≥ 0), and the two normal CDFs use DeFiMath.stdNormCDF.

The annualization step converts timeToExp (seconds) to a year fraction by dividing by SECONDS_IN_YEAR, then scales volatility by √T once and reuses the result throughout. The +1 on scaledVol keeps the division in d₁ well-defined even for zero-vol edge cases. The final assembly computes discountedStrike · Φ(−d₂) − spot · Φ(−d₁) and clamps at zero — Black-Scholes can produce slightly negative values (on the order of 10⁻¹²) due to rounding in the underlying primitives when the option is far out of the money. The 5.4e-12 max absolute error is benchmarked at spot = $1,000 across a full sweep of strike, time, vol, and rate — reproducible from defimath-compare.

Errors

ErrorTrigger
SpotLowerBoundErrorspot ≤ MIN_SPOT
SpotUpperBoundErrorspot ≥ MAX_SPOT
StrikeLowerBoundErrorstrike · 5 < spot
StrikeUpperBoundErrorspot · 5 < strike
TimeToExpiryUpperBoundErrortimeToExp ≥ MAX_EXPIRATION
RateUpperBoundErrorrate ≥ MAX_RATE

Example

solidity
import "defimath-lib/contracts/derivatives/Options.sol";

uint256 price = DeFiMathOptions.putOptionPrice(
    1000e18,         // spot = $1,000
    980e18,          // strike = $980
    60 days,         // 60 days to expiry
    0.60e18,         // 60% annualized vol
    0.05e18          // 5% risk-free rate
);
// price ≈ 71.4e18  (about $71.40 per option)