Diffusal

BlackScholesLib

Black-Scholes option pricing implementation

The BlackScholesLib and BlackScholesWad libraries implement the Black-Scholes pricing model for European options. These libraries are the mathematical core of Diffusal's pricing system, calculating option prices and Greeks with high precision using 64.64 fixed-point arithmetic.


Overview

The Black-Scholes libraries provide:

LibraryPurpose
BlackScholesLibCore pricing in 64.64 fixed-point format
BlackScholesWadWAD adapter for external interface

Architecture

┌─────────────────────────────────────────────┐
│         DiffusalOptionsQuoter               │
│           (external interface)              │
└──────────────────────┬──────────────────────┘


┌─────────────────────────────────────────────┐
│            BlackScholesWad                  │
│        (WAD <-> 64.64 conversion)           │
└──────────────────────┬──────────────────────┘


┌─────────────────────────────────────────────┐
│            BlackScholesLib                  │
│   (core 64.64 arithmetic + normalPdf/Cdf)     │
└──────────────────────┬──────────────────────┘


┌─────────────────────────────────────────────┐
│             ABDKMath64x64                   │
│        (ln, exp, sqrt, mul, div)            │
└─────────────────────────────────────────────┘

Key Concepts

Black-Scholes Formula

The Black-Scholes model prices European options. For the complete mathematical derivation and formula details, see Black-Scholes Model.

Call Option:

C=SN(d1)KerTN(d2)C = S \cdot N(d_1) - K \cdot e^{-rT} \cdot N(d_2)

Put Option:

P=KerTN(d2)SN(d1)P = K \cdot e^{-rT} \cdot N(-d_2) - S \cdot N(-d_1)

Option Greeks

Greeks measure option sensitivity to various parameters. See Black-Scholes: Greeks for complete formulas.

GreekMeaningRange (Call)
DeltaPrice sensitivity to spot0 to 1
GammaDelta sensitivity to spotAlways positive
VegaPrice sensitivity to volatilityAlways positive
ThetaTime decay (per year)Usually negative
RhoSensitivity to interest ratePositive (calls)

Precision Model

Format Conversion

┌─────────────────────────────────────────────────────────────┐
│                   Precision Hierarchy                       │
├─────────────────────────────────────────────────────────────┤
│  ┌───────────────────────────────────────────────────────┐  │
│  │         External Interface: WAD (1e18)                │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │  0.50e18 = 50% volatility                       │  │  │
│  │  │  3000e18 = $3000 spot price                     │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └────────────────────────┬──────────────────────────────┘  │
│                           │                                 │
│                           ▼                                 │
│  ┌───────────────────────────────────────────────────────┐  │
│  │      Internal Math: 64.64 fixed-point (int128)        │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │  Upper 64 bits = integer part                   │  │  │
│  │  │  Lower 64 bits = fractional part                │  │  │
│  │  │  Range: roughly +/-9.2e18 with ~18 decimal      │  │  │
│  │  │  precision                                      │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Conversion Functions

// WAD → 64.64
function wadTo64x64(uint256 wad) internal pure returns (int128) {
    return ((wad << 64) / 1e18).toInt256().toInt128();
}

// 64.64 → WAD (unsigned)
function from64x64ToWad(int128 x) internal pure returns (uint256) {
    return (uint256(int256(x)) * 1e18) >> 64;
}

// 64.64 → WAD (signed)
function from64x64ToSignedWad(int128 x) internal pure returns (int256) {
    // Handles negative values for Greeks
}

Normal Distribution Functions

BlackScholesLib includes inline implementations of the normal distribution functions required for Black-Scholes pricing.

Why 64.64 Fixed-Point?

┌─────────────────────────────────────────────────────────────┐
│                  64.64 Format (int128)                      │
├─────────────────────────────────────────────────────────────┤
│  [64 bits integer part] [64 bits fractional part]           │
│                                                             │
│  - ~18 decimal digits of precision                          │
│  - Range: approximately +/-9.2 x 10^18                      │
│  - Deterministic (no floating-point rounding)               │
│  - Efficient compared to arbitrary precision                │
└─────────────────────────────────────────────────────────────┘

_normalPdf (Internal)

Standard normal probability density function:

n(x)=12πex2/2n(x) = \frac{1}{\sqrt{2\pi}} e^{-x^2/2}

Used in Gamma and Theta calculations.

_normalCdf (Internal)

Standard normal cumulative distribution function using the Abramowitz-Stegun approximation (equation 26.2.17):

N(x)1n(x)(a1t+a2t2+a3t3+a4t4+a5t5)N(x) \approx 1 - n(x) \cdot (a_1t + a_2t^2 + a_3t^3 + a_4t^4 + a_5t^5)

Where t=11+pxt = \frac{1}{1 + px} and p=0.2316419p = 0.2316419

Properties:

  • Maximum error: < 7.5 × 10^-8
  • Deterministic across all nodes
  • Clamped for numerical stability at |x| ≥ 6

Internal Constants

// 1/√2π in 64.64 format
int128 private constant INV_SQRT_2PI_64X64 = 7_359_186_146_747_302_912;

// Abramowitz-Stegun coefficients
int128 private constant AS_P_64X64 = 4_273_038_846_047_820_800;    // p = 0.2316419
int128 private constant AS_A1_64X64 = 5_891_549_345_779_789_824;   // a1 = 0.319381530
int128 private constant AS_A2_64X64 = -6_577_440_832_507_964_416;  // a2 = -0.356563782
int128 private constant AS_A3_64X64 = 32_862_467_576_799_068_160;  // a3 = 1.781477937
int128 private constant AS_A4_64X64 = -33_596_242_918_879_592_448; // a4 = -1.821255978
int128 private constant AS_A5_64X64 = 24_539_231_939_563_106_304;  // a5 = 1.330274429

BlackScholesLib (64.64)

Types

Params64x64

Input parameters for pricing:

struct Params64x64 {
    int128 spot;          // S - Current price of underlying
    int128 strike;        // K - Strike price
    int128 rate;          // r - Risk-free interest rate
    int128 volatility;    // σ - Annualized volatility
    int128 timeToExpiry;  // T - Time to expiration in years
}

Greeks64x64

Option Greeks:

struct Greeks64x64 {
    int128 delta;   // ∂V/∂S - Rate of change with respect to spot
    int128 gamma;   // ∂²V/∂S² - Rate of change of delta
    int128 vega;    // ∂V/∂σ - Sensitivity to volatility
    int128 theta;   // ∂V/∂t - Time decay (per year)
    int128 rho;     // ∂V/∂r - Sensitivity to interest rate
}

PriceResult64x64

Complete pricing result:

struct PriceResult64x64 {
    int128 price;          // Option price
    int128 d1;             // d1 intermediate value
    int128 d2;             // d2 intermediate value
    Greeks64x64 greeks;    // Option Greeks
}

Core Functions

calculateD1D2

Calculates the d1 and d2 values:

function calculateD1D2(Params64x64 memory p)
    internal pure returns (int128 d1, int128 d2)

Implementation:

// σ√T
int128 sqrtT = ABDKMath64x64.sqrt(p.timeToExpiry);
int128 volSqrtT = p.volatility.mul(sqrtT);

// ln(S/K)
int128 ratio = p.spot.div(p.strike);
int128 logRatio = ABDKMath64x64.ln(ratio);

// (r + σ²/2)T
int128 volSquaredHalf = p.volatility.mul(p.volatility).mul(HALF);
int128 rateAdjustedT = (p.rate.add(volSquaredHalf)).mul(p.timeToExpiry);

// d1 = [ln(S/K) + (r + σ²/2)T] / (σ√T)
d1 = (logRatio.add(rateAdjustedT)).div(volSqrtT);

// d2 = d1 - σ√T
d2 = d1.sub(volSqrtT);

priceCall

Prices a European call option with full Greeks:

function priceCall(Params64x64 memory p)
    internal pure returns (PriceResult64x64 memory result)

Formula: C=SN(d1)KerTN(d2)C = S \cdot N(d_1) - K \cdot e^{-rT} \cdot N(d_2)


pricePut

Prices a European put option with full Greeks:

function pricePut(Params64x64 memory p)
    internal pure returns (PriceResult64x64 memory result)

Formula: P=KerTN(d2)SN(d1)P = K \cdot e^{-rT} \cdot N(-d_2) - S \cdot N(-d_1)


priceCallOnly / pricePutOnly

Optimized pricing without Greeks calculation:

function priceCallOnly(Params64x64 memory p) internal pure returns (int128 optionPrice)
function pricePutOnly(Params64x64 memory p) internal pure returns (int128 optionPrice)

Use case: When only the price is needed (e.g., getOptionQuote()).


BlackScholesWad (WAD Adapter)

Types

ParamsWad

struct ParamsWad {
    uint256 spot;          // S - Current price (WAD)
    uint256 strike;        // K - Strike price (WAD)
    uint256 rate;          // r - Risk-free rate (WAD, 0.05e18 = 5%)
    uint256 volatility;    // σ - Volatility (WAD, 0.50e18 = 50%)
    uint256 timeToExpiry;  // T - Time in years (WAD, 0.25e18 = 3 months)
}

GreeksWad

struct GreeksWad {
    int256 delta;   // Delta (WAD, range -1e18 to 1e18)
    int256 gamma;   // Gamma (WAD)
    int256 vega;    // Vega (WAD)
    int256 theta;   // Theta (WAD, per year)
    int256 rho;     // Rho (WAD)
}

PriceResultWad

struct PriceResultWad {
    uint256 price;      // Option price (WAD)
    GreeksWad greeks;   // Greeks (WAD)
}

Pricing Functions

priceCall / pricePut

Full pricing with Greeks:

function priceCall(ParamsWad memory p)
    internal pure returns (PriceResultWad memory result)

function pricePut(ParamsWad memory p)
    internal pure returns (PriceResultWad memory result)

priceCallOnly / pricePutOnly

Optimized pricing:

function priceCallOnly(ParamsWad memory p) internal pure returns (uint256 priceWad)
function pricePutOnly(ParamsWad memory p) internal pure returns (uint256 priceWad)

calculateDiscountFactor

Calculate the discount factor erTe^{-rT} in WAD format:

function calculateDiscountFactor(uint256 rate, uint256 timeToExpiry) internal pure returns (uint256 discount)

Parameters:

  • rate — Risk-free rate in WAD (e.g., 0.05e18 for 5%)
  • timeToExpiry — Time to expiry in years, WAD format

Returns: The discount factor erTe^{-rT} in WAD format

Use case: Put-Call Parity verification, forward price calculations, and any scenario requiring the present value factor. Uses the same 64.64 math path as pricing functions for consistency.


Pricing Example

Input Parameters

ParamsWad memory params = ParamsWad({
    spot: 3000e18,        // $3000
    strike: 3200e18,      // $3200
    rate: 0.05e18,        // 5% risk-free rate
    volatility: 0.60e18,  // 60% annualized volatility
    timeToExpiry: 0.25e18 // 3 months (0.25 years)
});

Call Option Price

PriceResultWad memory result = BlackScholesWad.priceCall(params);
// result.price ≈ 197.42e18 ($197.42 per contract)
// result.greeks.delta ≈ 0.423e18 (42.3%)
// result.greeks.gamma ≈ 0.0008e18
// result.greeks.vega ≈ 573.2e18
// result.greeks.theta ≈ -385.6e18 (per year)
// result.greeks.rho ≈ 273.4e18

Interpretation

GreekValueMeaning
Delta = 0.42342.3%$1 spot increase → $0.423 option increase
Gamma = 0.0008Delta increases by 0.08% per $1 spot move
Vega = 573.21% vol increase → $5.73 price increase
Theta = -385.6/365-$1.06/dayOption loses ~$1.06/day to time decay
Rho = 273.41% rate increase → $2.73 price increase

Integration Points

Depends On

LibraryPurpose
ABDKMath64x6464.64 fixed-point arithmetic (ln, exp, sqrt, mul, div)
ConstantsWAD, 64.64 constants

Note: The normal distribution functions (_normalPdf, _normalCdf) are implemented inline within BlackScholesLib using the Abramowitz-Stegun approximation for numerical efficiency.

Used By

ContractPurpose
DiffusalOptionsQuoterAll option pricing
DiffusalLiquidationEngineMark prices for liquidation
MarginEngineStress scenario pricing

Security Considerations

Numerical Precision

The 64.64 format provides ~18 decimal digits of precision:

  • Safe range: Prices and volatilities in practical ranges
  • Overflow risk: Very large spot/strike ratios (>10^9)
  • Underflow risk: Very small time to expiry or volatility

Numerical Stability Features

The library includes safeguards for numerical stability:

FeatureImplementationPurpose
Minimum time-to-expiryMIN_TIME_64X64 (1 minute)Prevents division by zero when σ√T → 0
normalCdf clampingValues |x| ≥ 6 clampedReturns 0 or 1 for extreme inputs
// Minimum time to expiry: 1 minute = 1/(365*24*60) years
int128 private constant MIN_TIME_64X64 = 35_100_917_606_222;

// In normalCdf:
if (x >= sixInt) return ONE;  // N(6) ≈ 1
if (x <= neg(sixInt)) return 0; // N(-6) ≈ 0

Edge Cases

| Scenario | Handling | | ------------------------- | ----------------------------------------------- | --- | ---- | | Zero volatility | Reverts with ZeroVolatility | | Time to expiry < 1 minute | Reverts with OptionExpired | | Very deep ITM/OTM | normalCdf clamped to 0 or 1 ( | x | ≥ 6) | | Negative price result | Clamped to 0 (shouldn't occur for valid inputs) |

Note: The minimum time-to-expiry check prevents numerical instability that occurs when dividing by σ√T as it approaches zero. This threshold (1 minute) was chosen to allow pricing of very short-dated options while maintaining numerical stability.

Input Validation

The libraries assume valid inputs. The DiffusalOptionsQuoter validates:

  • Non-zero volatility
  • Non-expired options
  • Non-zero spot and strike prices

Code Reference

Source:

  • packages/contracts/src/utils/BlackScholesLib.sol — Core 64.64 implementation
  • packages/contracts/src/utils/BlackScholesWad.sol — WAD adapter

Key Constants

// From Constants.sol (64.64 format)
int128 constant ONE_64X64 = 0x10000000000000000;   // 1.0
int128 constant TWO_64X64 = 0x20000000000000000;   // 2.0
int128 constant HALF_64X64 = 0x8000000000000000;   // 0.5

// WAD
uint256 constant WAD = 1e18;

On this page