Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Keystone

Deterministic financial computation infrastructure for DeFi and fintech.

What is Keystone?

Keystone provides precision arithmetic and financial calculations that produce identical results across all platforms. Built in Rust with no_std support, it compiles to native code, WebAssembly, and ZK-proving targets.

Key Features

  • 128-bit decimal arithmetic with 28 digits of precision
  • 7 rounding modes including banker’s rounding (half-even)
  • Checked operations that explicitly handle overflow
  • Cross-platform determinism verified in CI
  • DeFi risk metrics for health factors, liquidation, and position analysis
  • Financial calculations for interest, time value of money, and percentages

Crates

CrateDescription
precision-coreDecimal type with deterministic arithmetic
financial-calcInterest, TVM, and percentage calculations
risk-metricsDeFi health factors and liquidation logic
wasm-bindingsJavaScript/TypeScript bindings

Quick Example

#![allow(unused)]
fn main() {
use precision_core::{Decimal, RoundingMode};

let price = Decimal::new(123456, 2);  // 1234.56
let quantity = Decimal::new(15, 1);   // 1.5
let total = price.try_mul(quantity)?; // 1851.84

// Round to cents using banker's rounding
let rounded = total.round(2, RoundingMode::HalfEven);
}

Performance

Operations complete in nanoseconds:

OperationTime
Addition~8 ns
Multiplication~8 ns
Division~36 ns
Health factor~23 ns

License

Dual-licensed under MIT and Apache 2.0.

Installation

Rust

Add to your Cargo.toml:

[dependencies]
precision-core = "0.1"
financial-calc = "0.1"  # optional
risk-metrics = "0.1"    # optional

For no_std environments:

[dependencies]
precision-core = { version = "0.1", default-features = false }

JavaScript / TypeScript

npm install @dijkstra-keystone/wasm

Or via CDN:

<script type="module">
  import init, * as keystone from 'https://unpkg.com/@dijkstra-keystone/wasm';
  await init();
  console.log(keystone.add("0.1", "0.2")); // "0.3"
</script>

Building from Source

git clone https://github.com/dijkstra-keystone/keystone
cd keystone
cargo build --release

For WASM:

cargo build --target wasm32-unknown-unknown --release -p wasm-bindings

Quick Start

Basic Arithmetic

#![allow(unused)]
fn main() {
use precision_core::Decimal;

// From integers
let a = Decimal::from(100i64);

// From mantissa and scale: value = mantissa * 10^(-scale)
let b = Decimal::new(12345, 2);  // 123.45

// From strings
let c: Decimal = "99.99".parse().unwrap();

// Arithmetic with checked operations
let sum = a.try_add(b)?;
let product = a.try_mul(c)?;
let quotient = a.try_div(b)?;
}

Rounding

#![allow(unused)]
fn main() {
use precision_core::{Decimal, RoundingMode};

let value = Decimal::new(12345, 3);  // 12.345

// Banker's rounding (half-even) - default
let rounded = value.round_dp(2);  // 12.34

// Other modes
let up = value.round(2, RoundingMode::Up);        // 12.35
let down = value.round(2, RoundingMode::Down);    // 12.34
let half_up = value.round(2, RoundingMode::HalfUp); // 12.35
}

Financial Calculations

#![allow(unused)]
fn main() {
use financial_calc::{compound_interest, future_value, Decimal};

let principal = Decimal::from(10000i64);
let rate = Decimal::new(5, 2);  // 5%

// Compound interest: monthly for 5 years
let interest = compound_interest(principal, rate, 12, 5)?;

// Future value
let fv = future_value(principal, rate, 10)?;
}

Risk Metrics

#![allow(unused)]
fn main() {
use risk_metrics::{health_factor, liquidation_price, Decimal};

let collateral = Decimal::from(10000i64);
let debt = Decimal::from(5000i64);
let threshold = Decimal::new(80, 2);  // 80%

// Health factor: (collateral * threshold) / debt
let hf = health_factor(collateral, debt, threshold)?;  // 1.6

// Liquidation price
let liq = liquidation_price(
    Decimal::from(5i64),  // 5 ETH collateral
    debt,
    threshold,
)?;  // $1,250 per ETH
}

Error Handling

#![allow(unused)]
fn main() {
use precision_core::{Decimal, ArithmeticError};

let result = Decimal::MAX.try_add(Decimal::ONE);
match result {
    Ok(value) => println!("Result: {}", value),
    Err(ArithmeticError::Overflow) => println!("Overflow"),
    Err(ArithmeticError::DivisionByZero) => println!("Division by zero"),
    Err(e) => println!("Error: {}", e),
}
}

Decimal Type

The Decimal type provides 128-bit decimal arithmetic with up to 28 significant digits.

Construction

#![allow(unused)]
fn main() {
use precision_core::Decimal;

// Constants
let zero = Decimal::ZERO;
let one = Decimal::ONE;
let hundred = Decimal::ONE_HUNDRED;

// From integers (infallible)
let a = Decimal::from(42i64);
let b = Decimal::from(1_000_000u64);

// From mantissa and scale
// Value = mantissa * 10^(-scale)
let c = Decimal::new(12345, 2);    // 123.45
let d = Decimal::new(-500, 3);     // -0.500

// From strings (fallible)
let e: Decimal = "123.456".parse()?;
let f: Decimal = "-0.001".parse()?;

// From 128-bit integers (infallible)
let g = Decimal::from(i128::MAX);
}

Checked Arithmetic

All arithmetic operations have checked variants that return Option:

#![allow(unused)]
fn main() {
let a = Decimal::from(100i64);
let b = Decimal::from(3i64);

// Returns None on overflow/underflow/division-by-zero
let sum = a.checked_add(b);      // Some(103)
let diff = a.checked_sub(b);     // Some(97)
let prod = a.checked_mul(b);     // Some(300)
let quot = a.checked_div(b);     // Some(33.333...)
let rem = a.checked_rem(b);      // Some(1)

// Overflow example
let overflow = Decimal::MAX.checked_add(Decimal::ONE);  // None
}

Try Arithmetic

For explicit error handling:

#![allow(unused)]
fn main() {
use precision_core::{Decimal, ArithmeticError};

let a = Decimal::from(100i64);
let b = Decimal::ZERO;

match a.try_div(b) {
    Ok(result) => println!("{}", result),
    Err(ArithmeticError::DivisionByZero) => println!("Cannot divide by zero"),
    Err(ArithmeticError::Overflow) => println!("Result too large"),
    Err(e) => println!("Error: {}", e),
}
}

Saturating Arithmetic

Operations that clamp to MAX or MIN on overflow:

#![allow(unused)]
fn main() {
let max = Decimal::MAX;
let result = max.saturating_add(Decimal::ONE);  // MAX (no panic)
}

Properties

#![allow(unused)]
fn main() {
let a = Decimal::new(-12345, 3);  // -12.345

a.is_zero();        // false
a.is_negative();    // true
a.is_positive();    // false
a.scale();          // 3
a.abs();            // 12.345
a.signum();         // -1
}

Comparison

#![allow(unused)]
fn main() {
let a = Decimal::from(100i64);
let b = Decimal::from(200i64);

a < b;              // true
a.min(b);           // 100
a.max(b);           // 200
a.clamp(Decimal::ZERO, Decimal::from(150i64));  // 100
}

Normalization

Remove trailing zeros:

#![allow(unused)]
fn main() {
let a = Decimal::new(1000, 2);  // 10.00
let normalized = a.normalize();  // 10 (scale = 0)
}

Internal Representation

Access the underlying components:

#![allow(unused)]
fn main() {
let a = Decimal::new(12345, 3);
let (mantissa, scale) = a.to_parts();  // (12345, 3)

// Access rust_decimal directly if needed
let inner = a.into_inner();
}

Rounding Modes

Keystone supports 7 rounding modes for precise control over decimal rounding.

Available Modes

ModeDescription2.5 →3.5 →-2.5 →
HalfEvenBanker’s rounding (ties to even)24-2
HalfUpTies round away from zero34-3
HalfDownTies round toward zero23-2
UpAlways round toward +∞34-2
DownAlways round toward -∞23-3
TowardZeroTruncate (round toward zero)23-2
AwayFromZeroRound away from zero34-3

Usage

#![allow(unused)]
fn main() {
use precision_core::{Decimal, RoundingMode};

let value = Decimal::new(12345, 3);  // 12.345

// Round to 2 decimal places
value.round(2, RoundingMode::HalfEven);   // 12.34
value.round(2, RoundingMode::HalfUp);     // 12.35
value.round(2, RoundingMode::Down);       // 12.34
value.round(2, RoundingMode::Up);         // 12.35

// Convenience methods
value.round_dp(2);  // 12.34 (uses HalfEven)
value.trunc(2);     // 12.34 (uses TowardZero)
value.floor();      // 12 (round toward -∞)
value.ceil();       // 13 (round toward +∞)
}

Banker’s Rounding (HalfEven)

The default mode. Ties round to the nearest even digit. This minimizes cumulative rounding error over many operations.

#![allow(unused)]
fn main() {
let a = Decimal::new(25, 1);  // 2.5
let b = Decimal::new(35, 1);  // 3.5

a.round(0, RoundingMode::HalfEven);  // 2 (rounds to even)
b.round(0, RoundingMode::HalfEven);  // 4 (rounds to even)
}

Financial Applications

Use CaseRecommended Mode
Invoice totalsHalfUp
Tax calculationsHalfUp or Up (toward government)
Interest accumulationHalfEven
Currency displayHalfUp
Truncation (e.g., satoshis)TowardZero

WASM

import { round } from '@dijkstra-keystone/wasm';

round("2.345", 2, "half_even");  // "2.34"
round("2.345", 2, "half_up");    // "2.35"
round("2.345", 2, "truncate");   // "2.34"

Available mode strings: "down", "up", "toward_zero", "truncate", "away_from_zero", "half_even", "bankers", "half_up", "half_down".

Error Handling

Keystone uses explicit error types for all fallible operations.

Arithmetic Errors

#![allow(unused)]
fn main() {
use precision_core::ArithmeticError;

pub enum ArithmeticError {
    Overflow,       // Result exceeds MAX
    Underflow,      // Result below MIN
    DivisionByZero, // Division by zero
    ScaleExceeded,  // Scale > 28
}
}

Parse Errors

#![allow(unused)]
fn main() {
use precision_core::ParseError;

pub enum ParseError {
    Empty,                  // Empty string
    InvalidCharacter,       // Non-numeric character
    MultipleDecimalPoints,  // "1.2.3"
    OutOfRange,            // Value too large
}
}

Handling Patterns

Option-based (checked operations)

#![allow(unused)]
fn main() {
let a = Decimal::from(100i64);
let b = Decimal::ZERO;

match a.checked_div(b) {
    Some(result) => use_result(result),
    None => handle_error(),
}
}

Result-based (try operations)

#![allow(unused)]
fn main() {
let result = a.try_div(b);
match result {
    Ok(value) => use_value(value),
    Err(ArithmeticError::DivisionByZero) => handle_div_zero(),
    Err(e) => handle_other(e),
}
}

With the ? operator

#![allow(unused)]
fn main() {
fn calculate(a: Decimal, b: Decimal) -> Result<Decimal, ArithmeticError> {
    let sum = a.try_add(b)?;
    let product = sum.try_mul(Decimal::from(2i64))?;
    Ok(product)
}
}

Panicking Operations

Standard operators panic on error. Use only when errors are impossible:

#![allow(unused)]
fn main() {
// These panic on overflow or division by zero
let sum = a + b;
let diff = a - b;
let prod = a * b;
let quot = a / b;  // panics if b == 0
}

WASM Error Handling

JavaScript functions throw on error:

try {
  const result = keystone.divide("1", "0");
} catch (e) {
  console.error(e.message);  // "division by zero"
}

Or use optional chaining:

const result = (() => {
  try { return keystone.divide(a, b); }
  catch { return null; }
})();

Tolerance Comparisons

Financial calculations often require approximate equality checks due to rounding. Keystone provides tolerance-based comparison functions.

Absolute Tolerance

Check if two values differ by at most a fixed amount:

#![allow(unused)]
fn main() {
use precision_core::{Decimal, approx_eq};

let a = Decimal::new(10000, 2);  // 100.00
let b = Decimal::new(10001, 2);  // 100.01
let tolerance = Decimal::new(1, 2);  // 0.01

approx_eq(a, b, tolerance);  // true (difference <= 0.01)
}

Relative Tolerance

Check if two values differ by at most a percentage of their magnitude:

#![allow(unused)]
fn main() {
use precision_core::{Decimal, approx_eq_relative};

let a = Decimal::from(1_000_000i64);
let b = Decimal::from(1_000_100i64);
let tolerance = Decimal::new(1, 3);  // 0.1%

approx_eq_relative(a, b, tolerance);  // true
}

Combined Tolerance

Use both absolute and relative tolerances (passes if either succeeds):

#![allow(unused)]
fn main() {
use precision_core::{Decimal, approx_eq_ulps};

let a = Decimal::from(100i64);
let b = Decimal::new(10001, 2);  // 100.01

approx_eq_ulps(
    a, b,
    Decimal::new(1, 2),   // absolute: 0.01
    Decimal::new(1, 3),   // relative: 0.1%
);
}

Percentage Tolerance

Check if values are within a percentage of each other:

#![allow(unused)]
fn main() {
use precision_core::{Decimal, within_percentage};

let actual = Decimal::from(102i64);
let expected = Decimal::from(100i64);

within_percentage(actual, expected, Decimal::from(5i64));  // true (within 5%)
within_percentage(actual, expected, Decimal::from(1i64));  // false (not within 1%)
}

Basis Points Tolerance

For financial applications using basis points (1 bp = 0.01%):

#![allow(unused)]
fn main() {
use precision_core::{Decimal, within_basis_points};

let a = Decimal::new(10010, 2);  // 100.10
let b = Decimal::from(100i64);

within_basis_points(a, b, Decimal::from(100i64));  // true (within 100 bps = 1%)
within_basis_points(a, b, Decimal::from(5i64));    // false (not within 5 bps)
}

Use Cases

ScenarioRecommended Function
Comparing priceswithin_basis_points
Verifying calculationsapprox_eq with small absolute tolerance
Comparing large valuesapprox_eq_relative
Test assertionsapprox_eq_ulps (handles edge cases)
Rate comparisonswithin_percentage

Interest Calculations

The financial-calc crate provides precise interest calculation functions.

Simple Interest

Interest calculated only on the principal:

#![allow(unused)]
fn main() {
use financial_calc::{simple_interest, Decimal};

let principal = Decimal::from(10000i64);
let rate = Decimal::new(5, 2);  // 5% annual rate
let years = Decimal::from(3i64);

let interest = simple_interest(principal, rate, years)?;
// interest = 10000 * 0.05 * 3 = 1500
}

Formula: I = P × r × t

Compound Interest

Interest calculated on principal plus accumulated interest:

#![allow(unused)]
fn main() {
use financial_calc::{compound_interest, Decimal};

let principal = Decimal::from(10000i64);
let rate = Decimal::new(5, 2);  // 5% annual rate
let periods_per_year = 12;       // monthly compounding
let years = 5;

let total_interest = compound_interest(principal, rate, periods_per_year, years)?;
// Returns total interest earned over the period
}

Formula: A = P(1 + r/n)^(nt) where interest = A - P

Compounding Frequencies

FrequencyPeriods per Year
Annual1
Semi-annual2
Quarterly4
Monthly12
Daily365

Effective Annual Rate (EAR)

Convert a nominal rate to its effective annual equivalent:

#![allow(unused)]
fn main() {
use financial_calc::{effective_annual_rate, Decimal};

let nominal_rate = Decimal::new(5, 2);  // 5% nominal
let periods = 12;  // monthly compounding

let ear = effective_annual_rate(nominal_rate, periods)?;
// ear ≈ 5.116% (effective annual rate)
}

Formula: EAR = (1 + r/n)^n - 1

Continuous Compounding

For theoretical continuous compounding, use a high number of periods:

#![allow(unused)]
fn main() {
let rate = Decimal::new(5, 2);
let periods = 365 * 24;  // hourly approximation

let ear = effective_annual_rate(rate, periods)?;
// Approaches e^r - 1
}

Error Handling

All interest functions return Result<Decimal, ArithmeticError>:

#![allow(unused)]
fn main() {
use financial_calc::{compound_interest, Decimal};
use precision_core::ArithmeticError;

let result = compound_interest(
    Decimal::MAX,
    Decimal::from(100i64),
    12,
    100,
);

match result {
    Ok(interest) => println!("Interest: {}", interest),
    Err(ArithmeticError::Overflow) => println!("Calculation overflow"),
    Err(e) => println!("Error: {:?}", e),
}
}

Practical Examples

Savings Account

#![allow(unused)]
fn main() {
let deposit = Decimal::from(5000i64);
let apy = Decimal::new(425, 4);  // 4.25% APY
let months = 18;

// APY is already effective rate, so use simple calculation
let years = Decimal::new(months as i64, 0)
    .checked_div(Decimal::from(12i64))
    .unwrap();
let earnings = simple_interest(deposit, apy, years)?;
}

Loan Interest

#![allow(unused)]
fn main() {
let loan = Decimal::from(250000i64);
let apr = Decimal::new(675, 4);  // 6.75% APR
let years = 30;

// Total interest over loan lifetime (monthly compounding)
let total_interest = compound_interest(loan, apr, 12, years)?;
}

Time Value of Money

Functions for present and future value calculations.

Future Value

Calculate the future value of a present amount:

#![allow(unused)]
fn main() {
use financial_calc::{future_value, Decimal};

let present = Decimal::from(10000i64);
let rate = Decimal::new(7, 2);  // 7% annual return
let periods = 10;

let fv = future_value(present, rate, periods)?;
// fv ≈ 19,671.51
}

Formula: FV = PV × (1 + r)^n

Present Value

Calculate the present value of a future amount:

#![allow(unused)]
fn main() {
use financial_calc::{present_value, Decimal};

let future = Decimal::from(100000i64);
let rate = Decimal::new(5, 2);  // 5% discount rate
let periods = 20;

let pv = present_value(future, rate, periods)?;
// pv ≈ 37,688.95
}

Formula: PV = FV / (1 + r)^n

Net Present Value (NPV)

Evaluate investment profitability by discounting future cash flows:

#![allow(unused)]
fn main() {
use financial_calc::{net_present_value, Decimal};

let rate = Decimal::new(10, 2);  // 10% discount rate
let cash_flows = [
    Decimal::from(-100000i64),  // Initial investment (negative)
    Decimal::from(30000i64),    // Year 1
    Decimal::from(40000i64),    // Year 2
    Decimal::from(50000i64),    // Year 3
    Decimal::from(60000i64),    // Year 4
];

let npv = net_present_value(rate, &cash_flows)?;
// npv > 0 indicates profitable investment
}

NPV Decision Rule

NPVDecision
> 0Accept (creates value)
= 0Indifferent
< 0Reject (destroys value)

Practical Examples

Retirement Planning

#![allow(unused)]
fn main() {
// How much do I need to save monthly to reach $1M in 30 years?
let target = Decimal::from(1_000_000i64);
let annual_return = Decimal::new(7, 2);
let years = 30;

// First, find present value
let pv_target = present_value(target, annual_return, years)?;
// pv_target ≈ $131,367

// Then calculate monthly savings needed (simplified)
let months = years * 12;
let monthly = pv_target.checked_div(Decimal::from(months as i64)).unwrap();
}

Investment Comparison

#![allow(unused)]
fn main() {
// Compare two investments with different cash flow patterns

// Investment A: $50K now, returns $80K in 5 years
let rate = Decimal::new(8, 2);
let fv_a = future_value(Decimal::from(50000i64), rate, 5)?;
let profit_a = Decimal::from(80000i64).checked_sub(fv_a);

// Investment B: $50K now, returns $15K annually for 5 years
let cash_flows_b = [
    Decimal::from(-50000i64),
    Decimal::from(15000i64),
    Decimal::from(15000i64),
    Decimal::from(15000i64),
    Decimal::from(15000i64),
    Decimal::from(15000i64),
];
let npv_b = net_present_value(rate, &cash_flows_b)?;
}

Inflation Adjustment

#![allow(unused)]
fn main() {
// What will $100 be worth in 10 years with 3% inflation?
let amount = Decimal::from(100i64);
let inflation = Decimal::new(3, 2);
let years = 10;

// Future purchasing power (inverse of future value)
let future_purchasing_power = present_value(amount, inflation, years)?;
// ≈ $74.41 in today's dollars
}

Error Conditions

  • Overflow: Large values or many periods can overflow
  • DivisionByZero: Rate of exactly -100% in present_value
#![allow(unused)]
fn main() {
// Handle potential errors
match future_value(principal, rate, periods) {
    Ok(fv) => use_value(fv),
    Err(ArithmeticError::Overflow) => handle_overflow(),
    Err(e) => handle_error(e),
}
}

Percentage Operations

Precise percentage calculations for financial applications.

Basic Percentage

Calculate a percentage of a value:

#![allow(unused)]
fn main() {
use financial_calc::{percentage_of, Decimal};

let total = Decimal::from(1500i64);
let rate = Decimal::new(15, 2);  // 15%

let amount = percentage_of(total, rate)?;
// amount = 225 (15% of 1500)
}

Percentage Change

Calculate the percentage change between two values:

#![allow(unused)]
fn main() {
use financial_calc::{percentage_change, Decimal};

let old_price = Decimal::from(100i64);
let new_price = Decimal::from(125i64);

let change = percentage_change(old_price, new_price)?;
// change = 25% (0.25)
}

Formula: (new - old) / old × 100

Add Percentage

Add a percentage to a base value:

#![allow(unused)]
fn main() {
use financial_calc::{add_percentage, Decimal};

let price = Decimal::from(100i64);
let tax_rate = Decimal::new(825, 4);  // 8.25%

let total = add_percentage(price, tax_rate)?;
// total = 108.25
}

Subtract Percentage

Remove a percentage from a value:

#![allow(unused)]
fn main() {
use financial_calc::{subtract_percentage, Decimal};

let original = Decimal::from(100i64);
let discount = Decimal::new(20, 2);  // 20%

let final_price = subtract_percentage(original, discount)?;
// final_price = 80
}

Reverse Percentage

Find the original value before a percentage was added:

#![allow(unused)]
fn main() {
use financial_calc::{reverse_percentage, Decimal};

let total_with_tax = Decimal::new(10825, 2);  // 108.25
let tax_rate = Decimal::new(825, 4);  // 8.25%

let original = reverse_percentage(total_with_tax, tax_rate)?;
// original = 100.00
}

Practical Examples

Sales Tax Calculation

#![allow(unused)]
fn main() {
let subtotal = Decimal::new(15999, 2);  // $159.99
let state_tax = Decimal::new(6, 2);      // 6%
let local_tax = Decimal::new(225, 4);    // 2.25%

let state_amount = percentage_of(subtotal, state_tax)?;
let local_amount = percentage_of(subtotal, local_tax)?;
let total = subtotal
    .checked_add(state_amount)?
    .checked_add(local_amount)?;
}

Profit Margin

#![allow(unused)]
fn main() {
let revenue = Decimal::from(500000i64);
let costs = Decimal::from(350000i64);
let profit = revenue.checked_sub(costs)?;

// Profit margin = profit / revenue
let margin = profit
    .checked_mul(Decimal::ONE_HUNDRED)?
    .checked_div(revenue)?;
// margin = 30%
}

Compound Discounts

#![allow(unused)]
fn main() {
// Apply 20% off, then additional 10% off
let original = Decimal::from(100i64);
let first_discount = Decimal::new(20, 2);
let second_discount = Decimal::new(10, 2);

let after_first = subtract_percentage(original, first_discount)?;  // 80
let final_price = subtract_percentage(after_first, second_discount)?;  // 72

// Note: Not the same as 30% off (which would be 70)
}

Tip Calculator

#![allow(unused)]
fn main() {
let bill = Decimal::new(8567, 2);  // $85.67

let tip_15 = percentage_of(bill, Decimal::new(15, 2))?;  // $12.85
let tip_18 = percentage_of(bill, Decimal::new(18, 2))?;  // $15.42
let tip_20 = percentage_of(bill, Decimal::new(20, 2))?;  // $17.13
}

Basis Points

For financial applications, use basis points (1 bp = 0.01%):

#![allow(unused)]
fn main() {
use precision_core::{Decimal, within_basis_points};

let quoted_rate = Decimal::new(525, 4);   // 5.25%
let actual_rate = Decimal::new(5251, 5);  // 5.251%

// Check if within 5 basis points
within_basis_points(quoted_rate, actual_rate, Decimal::from(5i64));  // true
}
Basis PointsPercentage
1 bp0.01%
10 bp0.10%
25 bp0.25%
50 bp0.50%
100 bp1.00%

Options Pricing

Black-Scholes options pricing and Greeks calculations.

Overview

The financial-calc crate provides complete Black-Scholes-Merton model implementation including:

  • European call/put pricing
  • All five Greeks (delta, gamma, theta, vega, rho)
  • Implied volatility calculation
  • Standard normal CDF/PDF

Basic Usage

#![allow(unused)]
fn main() {
use financial_calc::options::{black_scholes_call, OptionParams};
use precision_core::Decimal;
use core::str::FromStr;

let params = OptionParams {
    spot: Decimal::from(100i64),           // Current price
    strike: Decimal::from(100i64),         // Strike price
    rate: Decimal::from_str("0.05")?,      // 5% risk-free rate
    time: Decimal::from_str("0.25")?,      // 3 months to expiry
    volatility: Decimal::from_str("0.2")?, // 20% annualized vol
};

let call_price = black_scholes_call(&params)?;
}

Greeks

Calculate all Greeks for risk management:

#![allow(unused)]
fn main() {
use financial_calc::options::{call_greeks, put_greeks};

let greeks = call_greeks(&params)?;

println!("Delta: {}", greeks.delta);  // Price sensitivity
println!("Gamma: {}", greeks.gamma);  // Delta sensitivity
println!("Theta: {}", greeks.theta);  // Time decay (per day)
println!("Vega: {}", greeks.vega);    // Vol sensitivity (per 1%)
println!("Rho: {}", greeks.rho);      // Rate sensitivity (per 1%)
}

Implied Volatility

Recover implied volatility from market prices using Newton-Raphson:

#![allow(unused)]
fn main() {
use financial_calc::options::implied_volatility;

let market_price = Decimal::from_str("10.5")?;
let iv = implied_volatility(
    market_price,
    &params,
    true,  // is_call
    None,  // max_iterations (default: 100)
    None,  // tolerance (default: 0.0001)
)?;
}

Put-Call Parity

The implementation satisfies put-call parity:

C - P = S - K * e^(-rT)

Both call and put prices are internally consistent.

Accuracy

  • Uses Hart approximation (1968) for normal CDF
  • Maximum CDF error: ~7.5×10⁻⁸
  • All calculations use deterministic decimal arithmetic

AMM & DEX Calculations

Automated Market Maker calculations including concentrated liquidity (Uniswap V3-style).

Overview

The financial-calc::amm module provides:

  • Constant product (x*y=k) swap calculations
  • Concentrated liquidity math
  • Tick and sqrt price conversions
  • Impermanent loss calculations
  • Liquidity provision math

Constant Product Swaps

Calculate output amount for a swap:

#![allow(unused)]
fn main() {
use financial_calc::amm::calculate_swap_output;
use precision_core::Decimal;

let output = calculate_swap_output(
    Decimal::from(1_000_000i64),  // reserve_in
    Decimal::from(1_000_000i64),  // reserve_out
    Decimal::from(1_000i64),      // amount_in
    Decimal::from(30i64),         // fee_bps (0.3%)
)?;
}

Calculate required input for desired output:

#![allow(unused)]
fn main() {
use financial_calc::amm::calculate_swap_input;

let input = calculate_swap_input(
    Decimal::from(1_000_000i64),  // reserve_in
    Decimal::from(1_000_000i64),  // reserve_out
    Decimal::from(1_000i64),      // amount_out
    Decimal::from(30i64),         // fee_bps
)?;
}

Price Impact

Calculate price impact of a trade:

#![allow(unused)]
fn main() {
use financial_calc::amm::calculate_price_impact;

let impact = calculate_price_impact(
    Decimal::from(1_000_000i64),  // reserve_in
    Decimal::from(1_000_000i64),  // reserve_out
    Decimal::from(10_000i64),     // amount_in (1% of reserves)
)?;
// Returns ~0.01 (1% impact)
}

Concentrated Liquidity

Tick Math

Convert between ticks and sqrt prices:

#![allow(unused)]
fn main() {
use financial_calc::amm::{tick_to_sqrt_price, sqrt_price_to_tick};

// Tick to sqrt price
let sqrt_price = tick_to_sqrt_price(1000)?;

// Sqrt price to tick
let tick = sqrt_price_to_tick(sqrt_price)?;
}

Tick spacing constants:

#![allow(unused)]
fn main() {
use financial_calc::amm::{TICK_SPACING_LOW, TICK_SPACING_MEDIUM, TICK_SPACING_HIGH};

// 0.05% fee tier: spacing = 10
// 0.30% fee tier: spacing = 60
// 1.00% fee tier: spacing = 200
}

Liquidity Calculations

Calculate liquidity from token amounts:

#![allow(unused)]
fn main() {
use financial_calc::amm::calculate_liquidity_from_amounts;

let liquidity = calculate_liquidity_from_amounts(
    sqrt_price_current,
    sqrt_price_lower,
    sqrt_price_upper,
    amount_0,
    amount_1,
)?;
}

Calculate token amounts from liquidity:

#![allow(unused)]
fn main() {
use financial_calc::amm::calculate_amounts_from_liquidity;

let (amount_0, amount_1) = calculate_amounts_from_liquidity(
    sqrt_price_current,
    sqrt_price_lower,
    sqrt_price_upper,
    liquidity,
)?;
}

Position Value

Calculate current value of a concentrated position:

#![allow(unused)]
fn main() {
use financial_calc::amm::calculate_position_value;

let value = calculate_position_value(
    sqrt_price_current,
    sqrt_price_lower,
    sqrt_price_upper,
    liquidity,
)?;
}

Impermanent Loss

Calculate IL for a concentrated position:

#![allow(unused)]
fn main() {
use financial_calc::amm::calculate_impermanent_loss;

let il = calculate_impermanent_loss(
    entry_sqrt_price,
    current_sqrt_price,
    sqrt_price_lower,
    sqrt_price_upper,
    liquidity,
)?;
// Returns negative value (e.g., -0.05 for 5% loss vs HODL)
}

Full-Range Liquidity

For Uniswap V2-style pools:

#![allow(unused)]
fn main() {
use financial_calc::amm::{calculate_liquidity_mint, calculate_liquidity_burn};

// Mint LP tokens
let shares = calculate_liquidity_mint(
    amount_0,
    amount_1,
    reserve_0,
    reserve_1,
    total_supply,
)?;

// Burn LP tokens
let (out_0, out_1) = calculate_liquidity_burn(
    shares,
    reserve_0,
    reserve_1,
    total_supply,
)?;
}

Derivatives & Perpetuals

Perpetual futures calculations for protocols like GMX, Vertex, and Vela.

Overview

The financial-calc::derivatives module provides:

  • PnL calculations for perpetual positions
  • Liquidation price and distance
  • Funding rate calculations
  • Margin and leverage metrics
  • Breakeven price accounting for fees

Position Structure

#![allow(unused)]
fn main() {
use financial_calc::derivatives::PerpPosition;
use precision_core::Decimal;

let position = PerpPosition {
    size: Decimal::from_str("1.5")?,        // 1.5 ETH
    entry_price: Decimal::from(2000i64),    // $2000
    is_long: true,
    leverage: Decimal::from(10i64),         // 10x
    collateral: Decimal::from(300i64),      // $300
};
}

PnL Calculations

Calculate profit/loss:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::{calculate_pnl, calculate_pnl_percentage, calculate_roe};

let current_price = Decimal::from(2200i64);

// Absolute PnL
let pnl = calculate_pnl(&position, current_price)?;
// Long: (2200 - 2000) * 1.5 = $300 profit

// PnL as percentage of collateral
let pnl_pct = calculate_pnl_percentage(&position, current_price)?;
// 300 / 300 = 100%

// Return on Equity
let roe = calculate_roe(&position, current_price)?;
// 100%
}

Liquidation

Calculate liquidation price:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::{calculate_liquidation_price, calculate_liquidation_distance};

let maintenance_margin_rate = Decimal::from_str("0.01")?; // 1%

let liq_price = calculate_liquidation_price(&position, maintenance_margin_rate)?;
// For long: entry - (collateral - maintenance) / size

let distance = calculate_liquidation_distance(
    &position,
    current_price,
    maintenance_margin_rate,
)?;
// Returns percentage distance to liquidation
}

Funding Rate

Calculate funding rate using mark-index premium model:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::{calculate_funding_rate, FundingParams};

let params = FundingParams {
    mark_price: Decimal::from(2020i64),   // Mark above index
    index_price: Decimal::from(2000i64),
    interest_rate: Decimal::ZERO,
    premium_cap: Decimal::from_str("0.01")?, // 1% cap
    funding_interval_hours: Decimal::from(8i64),
};

let rate = calculate_funding_rate(&params)?;
// Premium = (2020 - 2000) / 2000 = 1%
}

Calculate funding payment:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::calculate_funding_payment;

let payment = calculate_funding_payment(
    &position,
    mark_price,
    funding_rate,
)?;
// Positive = receive, Negative = pay
// Longs pay positive funding, shorts receive
}

Leverage Metrics

Track effective leverage as price moves:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::{calculate_effective_leverage, calculate_margin_ratio};

let eff_leverage = calculate_effective_leverage(&position, current_price)?;
// Notional / (Collateral + Unrealized PnL)

let margin_ratio = calculate_margin_ratio(&position, current_price)?;
// Inverse of effective leverage
}

Position Sizing

Calculate max position or required collateral:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::{calculate_max_position_size, calculate_required_collateral};

let max_size = calculate_max_position_size(
    collateral,
    leverage,
    entry_price,
)?;

let required = calculate_required_collateral(
    size,
    entry_price,
    leverage,
)?;
}

Breakeven Price

Account for trading fees:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::calculate_breakeven_price;

let breakeven = calculate_breakeven_price(
    &position,
    Decimal::from_str("0.001")?, // 0.1% open fee
    Decimal::from_str("0.001")?, // 0.1% close fee
)?;
}

Average Entry Price

When adding to an existing position:

#![allow(unused)]
fn main() {
use financial_calc::derivatives::calculate_average_entry_price;

let avg = calculate_average_entry_price(
    existing_size,
    existing_avg_price,
    additional_size,
    additional_price,
)?;
}

Health Factor

The health factor is a key metric for collateralized lending positions in DeFi protocols.

Definition

Health factor measures the safety of a collateralized position:

Health Factor = (Collateral Value × Liquidation Threshold) / Debt Value
Health FactorStatus
> 1.0Safe
= 1.0At liquidation threshold
< 1.0Liquidatable

Basic Usage

#![allow(unused)]
fn main() {
use risk_metrics::{health_factor, Decimal};

let collateral = Decimal::from(10000i64);  // $10,000 collateral
let debt = Decimal::from(5000i64);          // $5,000 debt
let threshold = Decimal::new(80, 2);        // 80% liquidation threshold

let hf = health_factor(collateral, debt, threshold)?;
// hf = (10000 × 0.80) / 5000 = 1.6
}

Health Check

Quick boolean check for position safety:

#![allow(unused)]
fn main() {
use risk_metrics::{is_healthy, Decimal};

let collateral = Decimal::from(10000i64);
let debt = Decimal::from(8500i64);
let threshold = Decimal::new(80, 2);

is_healthy(collateral, debt, threshold)?;  // false (HF < 1.0)
}

Collateral Ratio

Calculate the ratio of collateral to debt:

#![allow(unused)]
fn main() {
use risk_metrics::{collateral_ratio, Decimal};

let collateral = Decimal::from(15000i64);
let debt = Decimal::from(10000i64);

let ratio = collateral_ratio(collateral, debt)?;
// ratio = 1.5 (150% collateralization)
}

Practical Examples

Monitor Position Safety

#![allow(unused)]
fn main() {
use risk_metrics::{health_factor, Decimal};

let collateral = Decimal::from(50000i64);
let debt = Decimal::from(30000i64);
let threshold = Decimal::new(825, 3);  // 82.5%

let hf = health_factor(collateral, debt, threshold)?;

match hf {
    hf if hf >= Decimal::new(15, 1) => println!("Position is safe"),
    hf if hf >= Decimal::new(12, 1) => println!("Consider adding collateral"),
    hf if hf >= Decimal::ONE => println!("Warning: Near liquidation"),
    _ => println!("DANGER: Position liquidatable"),
}
}

Calculate Safe Borrow Amount

#![allow(unused)]
fn main() {
use risk_metrics::{health_factor, Decimal};

let collateral = Decimal::from(100000i64);
let threshold = Decimal::new(80, 2);
let target_hf = Decimal::new(15, 1);  // Target 1.5 health factor

// max_debt = (collateral × threshold) / target_hf
let max_safe_debt = collateral
    .checked_mul(threshold)?
    .checked_div(target_hf)?;
// max_safe_debt = $53,333.33
}

Multi-Asset Position

#![allow(unused)]
fn main() {
// Weighted average for multiple collateral types
let eth_value = Decimal::from(50000i64);
let eth_threshold = Decimal::new(80, 2);

let btc_value = Decimal::from(30000i64);
let btc_threshold = Decimal::new(75, 2);

let weighted_collateral = eth_value
    .checked_mul(eth_threshold)?
    .checked_add(btc_value.checked_mul(btc_threshold)?)?;

let debt = Decimal::from(40000i64);
let hf = weighted_collateral.checked_div(debt)?;
}

Protocol-Specific Thresholds

Different protocols use different liquidation thresholds:

ProtocolTypical Threshold
Aave (ETH)82.5%
Compound (ETH)82.5%
MakerDAO (ETH-A)83%
Aave (Stablecoins)90%

Edge Cases

#![allow(unused)]
fn main() {
use risk_metrics::{health_factor, Decimal};
use precision_core::ArithmeticError;

// Zero debt = infinite health (returns MAX)
let hf = health_factor(
    Decimal::from(1000i64),
    Decimal::ZERO,
    Decimal::new(80, 2),
)?;
// hf = Decimal::MAX

// Zero collateral with debt
let hf = health_factor(
    Decimal::ZERO,
    Decimal::from(1000i64),
    Decimal::new(80, 2),
)?;
// hf = 0
}

Liquidation Calculations

Functions for calculating liquidation thresholds and prices in DeFi lending.

Liquidation Price

Calculate the price at which a position becomes liquidatable:

#![allow(unused)]
fn main() {
use risk_metrics::{liquidation_price, Decimal};

let collateral_amount = Decimal::from(5i64);  // 5 ETH
let debt = Decimal::from(10000i64);            // $10,000 debt
let threshold = Decimal::new(80, 2);           // 80% threshold

let liq_price = liquidation_price(collateral_amount, debt, threshold)?;
// liq_price = $2,500 per ETH
}

Formula: Liquidation Price = Debt / (Collateral Amount × Threshold)

When ETH drops to $2,500, the position reaches HF = 1.0 and becomes liquidatable.

Liquidation Threshold

Determine the threshold at which liquidation occurs:

#![allow(unused)]
fn main() {
use risk_metrics::{liquidation_threshold, Decimal};

let collateral_value = Decimal::from(10000i64);
let debt = Decimal::from(8000i64);
let current_hf = Decimal::new(125, 2);  // 1.25

let threshold = liquidation_threshold(collateral_value, debt, current_hf)?;
// Calculates the effective liquidation threshold
}

Maximum Borrowable

Calculate maximum debt for a given health factor target:

#![allow(unused)]
fn main() {
use risk_metrics::{max_borrowable, Decimal};

let collateral = Decimal::from(100000i64);
let threshold = Decimal::new(80, 2);
let min_health_factor = Decimal::new(15, 1);  // Target HF 1.5

let max_debt = max_borrowable(collateral, threshold, min_health_factor)?;
// max_debt = $53,333.33
}

Formula: Max Debt = (Collateral × Threshold) / Min Health Factor

Practical Examples

Position Monitoring Dashboard

#![allow(unused)]
fn main() {
use risk_metrics::{liquidation_price, health_factor, Decimal};

struct Position {
    eth_amount: Decimal,
    eth_price: Decimal,
    debt_usd: Decimal,
    threshold: Decimal,
}

fn analyze_position(pos: &Position) -> Result<(), ArithmeticError> {
    let collateral_usd = pos.eth_amount.checked_mul(pos.eth_price)?;

    let hf = health_factor(collateral_usd, pos.debt_usd, pos.threshold)?;
    let liq_price = liquidation_price(pos.eth_amount, pos.debt_usd, pos.threshold)?;

    let buffer = pos.eth_price
        .checked_sub(liq_price)?
        .checked_div(pos.eth_price)?
        .checked_mul(Decimal::ONE_HUNDRED)?;

    println!("Health Factor: {}", hf);
    println!("Liquidation Price: ${}", liq_price);
    println!("Price Buffer: {}%", buffer);

    Ok(())
}
}

Liquidation Alert System

#![allow(unused)]
fn main() {
use risk_metrics::{liquidation_price, Decimal};

fn check_liquidation_risk(
    collateral_amount: Decimal,
    debt: Decimal,
    threshold: Decimal,
    current_price: Decimal,
) -> Result<RiskLevel, ArithmeticError> {
    let liq_price = liquidation_price(collateral_amount, debt, threshold)?;

    let distance = current_price
        .checked_sub(liq_price)?
        .checked_div(current_price)?;

    Ok(match distance {
        d if d >= Decimal::new(30, 2) => RiskLevel::Safe,
        d if d >= Decimal::new(15, 2) => RiskLevel::Moderate,
        d if d >= Decimal::new(5, 2) => RiskLevel::High,
        _ => RiskLevel::Critical,
    })
}

enum RiskLevel {
    Safe,
    Moderate,
    High,
    Critical,
}
}

Multi-Collateral Liquidation

#![allow(unused)]
fn main() {
// Calculate weighted liquidation threshold for multiple assets
fn weighted_liquidation_threshold(
    assets: &[(Decimal, Decimal)],  // (value, threshold) pairs
) -> Result<Decimal, ArithmeticError> {
    let mut weighted_sum = Decimal::ZERO;
    let mut total_value = Decimal::ZERO;

    for (value, threshold) in assets {
        weighted_sum = weighted_sum
            .checked_add(value.checked_mul(*threshold)?)?;
        total_value = total_value.checked_add(*value)?;
    }

    weighted_sum.checked_div(total_value)
}
}

Liquidation Mechanics

Typical Liquidation Process

  1. Health factor drops below 1.0
  2. Liquidator repays portion of debt
  3. Liquidator receives collateral + bonus
  4. Position health factor improves

Liquidation Bonus

#![allow(unused)]
fn main() {
// Calculate liquidator profit
let debt_to_repay = Decimal::from(1000i64);
let liquidation_bonus = Decimal::new(5, 2);  // 5% bonus

let collateral_received = debt_to_repay
    .checked_mul(Decimal::ONE.checked_add(liquidation_bonus)?)?;
// Liquidator receives $1,050 worth of collateral for repaying $1,000
}

Edge Cases

#![allow(unused)]
fn main() {
// Zero collateral amount
let result = liquidation_price(
    Decimal::ZERO,
    Decimal::from(1000i64),
    Decimal::new(80, 2),
);
// Returns error or MAX (no liquidation price exists)

// Zero debt
let result = liquidation_price(
    Decimal::from(10i64),
    Decimal::ZERO,
    Decimal::new(80, 2),
);
// Returns ZERO (no liquidation risk)
}

Position Management

Functions for managing and analyzing trading and lending positions.

Position Size

Calculate appropriate position size based on risk parameters:

#![allow(unused)]
fn main() {
use risk_metrics::{position_size, Decimal};

let account_balance = Decimal::from(100000i64);
let risk_per_trade = Decimal::new(2, 2);  // 2% risk
let stop_loss_pct = Decimal::new(5, 2);   // 5% stop loss

let size = position_size(account_balance, risk_per_trade, stop_loss_pct)?;
// size = $40,000 (2% risk / 5% stop = 40% of account)
}

Leverage Calculation

Determine effective leverage of a position:

#![allow(unused)]
fn main() {
use risk_metrics::{effective_leverage, Decimal};

let position_value = Decimal::from(50000i64);
let collateral = Decimal::from(10000i64);

let leverage = effective_leverage(position_value, collateral)?;
// leverage = 5x
}

Margin Requirements

Calculate required margin for a leveraged position:

#![allow(unused)]
fn main() {
use risk_metrics::{required_margin, Decimal};

let position_size = Decimal::from(100000i64);
let leverage = Decimal::from(10i64);

let margin = required_margin(position_size, leverage)?;
// margin = $10,000
}

Profit and Loss

Unrealized PnL

#![allow(unused)]
fn main() {
use risk_metrics::{unrealized_pnl, Decimal};

let entry_price = Decimal::from(2000i64);
let current_price = Decimal::from(2150i64);
let position_size = Decimal::from(5i64);  // 5 ETH
let is_long = true;

let pnl = unrealized_pnl(entry_price, current_price, position_size, is_long)?;
// pnl = +$750
}

Return on Investment

#![allow(unused)]
fn main() {
use risk_metrics::{position_roi, Decimal};

let entry_value = Decimal::from(10000i64);
let current_value = Decimal::from(12500i64);

let roi = position_roi(entry_value, current_value)?;
// roi = 25%
}

Risk-Adjusted Returns

Sharpe Ratio Components

#![allow(unused)]
fn main() {
use risk_metrics::{excess_return, Decimal};

let portfolio_return = Decimal::new(12, 2);  // 12%
let risk_free_rate = Decimal::new(4, 2);     // 4%

let excess = excess_return(portfolio_return, risk_free_rate)?;
// excess = 8%
}

Practical Examples

Position Sizing with Kelly Criterion

#![allow(unused)]
fn main() {
use risk_metrics::Decimal;

fn kelly_fraction(
    win_probability: Decimal,
    win_loss_ratio: Decimal,
) -> Result<Decimal, ArithmeticError> {
    // Kelly % = W - (1-W)/R
    // where W = win probability, R = win/loss ratio
    let loss_probability = Decimal::ONE.checked_sub(win_probability)?;
    let second_term = loss_probability.checked_div(win_loss_ratio)?;
    win_probability.checked_sub(second_term)
}

let win_rate = Decimal::new(55, 2);    // 55% win rate
let win_loss = Decimal::new(15, 1);    // 1.5:1 win/loss ratio

let kelly = kelly_fraction(win_rate, win_loss)?;
// Use half-Kelly for safety: kelly / 2
}

Portfolio Position Limits

#![allow(unused)]
fn main() {
fn validate_position(
    new_position: Decimal,
    portfolio_value: Decimal,
    max_concentration: Decimal,  // e.g., 10%
) -> bool {
    let concentration = new_position
        .checked_div(portfolio_value)
        .unwrap_or(Decimal::MAX);

    concentration <= max_concentration
}
}

Liquidation-Safe Position

#![allow(unused)]
fn main() {
use risk_metrics::{max_borrowable, health_factor, Decimal};

fn safe_position_size(
    available_collateral: Decimal,
    threshold: Decimal,
    target_hf: Decimal,
    price_buffer: Decimal,  // e.g., 20% buffer for volatility
) -> Result<Decimal, ArithmeticError> {
    // Reduce effective collateral by price buffer
    let buffered_collateral = available_collateral
        .checked_mul(Decimal::ONE.checked_sub(price_buffer)?)?;

    max_borrowable(buffered_collateral, threshold, target_hf)
}
}

Break-Even Analysis

#![allow(unused)]
fn main() {
fn break_even_price(
    entry_price: Decimal,
    position_size: Decimal,
    fees: Decimal,
    is_long: bool,
) -> Result<Decimal, ArithmeticError> {
    let fee_per_unit = fees.checked_div(position_size)?;

    if is_long {
        entry_price.checked_add(fee_per_unit)
    } else {
        entry_price.checked_sub(fee_per_unit)
    }
}
}

Position Tracking

#![allow(unused)]
fn main() {
struct Position {
    asset: String,
    entry_price: Decimal,
    size: Decimal,
    is_long: bool,
    stop_loss: Option<Decimal>,
    take_profit: Option<Decimal>,
}

impl Position {
    fn value_at(&self, price: Decimal) -> Result<Decimal, ArithmeticError> {
        self.size.checked_mul(price)
    }

    fn pnl_at(&self, price: Decimal) -> Result<Decimal, ArithmeticError> {
        let diff = if self.is_long {
            price.checked_sub(self.entry_price)?
        } else {
            self.entry_price.checked_sub(price)?
        };
        diff.checked_mul(self.size)
    }

    fn should_close(&self, price: Decimal) -> bool {
        if let Some(sl) = self.stop_loss {
            if (self.is_long && price <= sl) || (!self.is_long && price >= sl) {
                return true;
            }
        }
        if let Some(tp) = self.take_profit {
            if (self.is_long && price >= tp) || (!self.is_long && price <= tp) {
                return true;
            }
        }
        false
    }
}
}

Browser Integration

Using Keystone in web applications via WebAssembly.

Installation

npm install @dijkstra-keystone/wasm
# or
yarn add @dijkstra-keystone/wasm

Setup

ES Modules

import init, * as keystone from '@dijkstra-keystone/wasm';

async function setup() {
  await init();
  // Now ready to use
  const result = keystone.add("100.50", "49.50");
  console.log(result); // "150"
}

Bundlers (Webpack, Vite, etc.)

Most bundlers handle WASM automatically:

// vite.config.js
export default {
  optimizeDeps: {
    exclude: ['@dijkstra-keystone/wasm']
  }
}
// In your code
import * as keystone from '@dijkstra-keystone/wasm';

const result = keystone.multiply("99.99", "1.0825");

Script Tag

<script type="module">
  import init, * as keystone from './keystone_wasm.js';

  init().then(() => {
    document.getElementById('result').textContent =
      keystone.add("1.1", "2.2");
  });
</script>

Basic Usage

All functions accept string representations of decimals:

import * as keystone from '@dijkstra-keystone/wasm';

// Arithmetic
keystone.add("100", "50");           // "150"
keystone.subtract("100", "30");      // "70"
keystone.multiply("25", "4");        // "100"
keystone.divide("100", "3");         // "33.333..."

// Comparison
keystone.compare("100", "200");      // -1 (less than)
keystone.compare("200", "200");      // 0 (equal)
keystone.compare("300", "200");      // 1 (greater than)

// Properties
keystone.abs("-42");                 // "42"
keystone.is_zero("0.00");            // true
keystone.is_negative("-5");          // true

Rounding

import { round } from '@dijkstra-keystone/wasm';

// Round to 2 decimal places
round("123.456", 2, "half_even");   // "123.46" (banker's)
round("123.456", 2, "half_up");     // "123.46"
round("123.455", 2, "half_up");     // "123.46"
round("123.454", 2, "half_up");     // "123.45"

// Available modes
round("2.5", 0, "half_even");       // "2" (ties to even)
round("2.5", 0, "half_up");         // "3" (ties away from zero)
round("2.5", 0, "down");            // "2" (toward -infinity)
round("2.5", 0, "up");              // "3" (toward +infinity)
round("2.5", 0, "toward_zero");     // "2" (truncate)
round("2.5", 0, "away_from_zero");  // "3"

Financial Functions

import * as keystone from '@dijkstra-keystone/wasm';

// Simple interest
keystone.simple_interest("10000", "0.05", "3");
// 10000 * 0.05 * 3 = "1500"

// Compound interest (principal, rate, periods_per_year, years)
keystone.compound_interest("10000", "0.05", 12, 5);
// Returns total interest earned

// Future value
keystone.future_value("10000", "0.07", 10);
// 10000 * (1.07)^10

// Present value
keystone.present_value("19672", "0.07", 10);
// Discounts future value to present

Risk Metrics

import * as keystone from '@dijkstra-keystone/wasm';

// Health factor
keystone.health_factor("10000", "5000", "0.80");
// (10000 * 0.80) / 5000 = "1.6"

// Is position healthy?
keystone.is_healthy("10000", "5000", "0.80");  // true

// Liquidation price
keystone.liquidation_price("5", "10000", "0.80");
// Price per unit at which position becomes liquidatable

Error Handling

Functions throw on error:

import * as keystone from '@dijkstra-keystone/wasm';

try {
  const result = keystone.divide("100", "0");
} catch (e) {
  console.error(e.message);  // "division by zero"
}

// Or use optional chaining pattern
function safeDivide(a, b) {
  try {
    return keystone.divide(a, b);
  } catch {
    return null;
  }
}

React Integration

import { useState, useEffect } from 'react';
import init, * as keystone from '@dijkstra-keystone/wasm';

function Calculator() {
  const [ready, setReady] = useState(false);
  const [result, setResult] = useState('');

  useEffect(() => {
    init().then(() => setReady(true));
  }, []);

  const calculate = (a, b) => {
    if (!ready) return;
    try {
      setResult(keystone.add(a, b));
    } catch (e) {
      setResult(`Error: ${e.message}`);
    }
  };

  return (
    <div>
      {ready ? (
        <button onClick={() => calculate("100.50", "49.50")}>
          Add
        </button>
      ) : (
        <span>Loading...</span>
      )}
      <div>Result: {result}</div>
    </div>
  );
}

Vue Integration

<script setup>
import { ref, onMounted } from 'vue';
import init, * as keystone from '@dijkstra-keystone/wasm';

const ready = ref(false);
const result = ref('');

onMounted(async () => {
  await init();
  ready.value = true;
});

function calculate() {
  result.value = keystone.multiply("99.99", "1.0825");
}
</script>

<template>
  <button v-if="ready" @click="calculate">Calculate</button>
  <span v-else>Loading...</span>
  <div>{{ result }}</div>
</template>

Performance Tips

  1. Batch operations: Minimize JS-WASM boundary crossings
  2. Reuse results: Store intermediate values in JS when possible
  3. Async init: Initialize WASM during app startup, not on-demand
// Pre-compute values
const TAX_RATE = "0.0825";
const DISCOUNT = "0.15";

function calculateTotal(items) {
  let subtotal = "0";
  for (const item of items) {
    subtotal = keystone.add(subtotal, item.price);
  }

  const discount = keystone.multiply(subtotal, DISCOUNT);
  const afterDiscount = keystone.subtract(subtotal, discount);
  const tax = keystone.multiply(afterDiscount, TAX_RATE);

  return keystone.add(afterDiscount, tax);
}

WASM API Reference

Complete API reference for the WebAssembly bindings.

Arithmetic Operations

add(a, b)

Add two decimal values.

add("100.50", "49.50")  // "150"
add("-10", "5")         // "-5"

Parameters:

  • a: string - First operand
  • b: string - Second operand

Returns: string - Sum

Throws: On overflow or invalid input


subtract(a, b)

Subtract b from a.

subtract("100", "30")   // "70"
subtract("10", "25")    // "-15"

Parameters:

  • a: string - Minuend
  • b: string - Subtrahend

Returns: string - Difference


multiply(a, b)

Multiply two decimal values.

multiply("25", "4")     // "100"
multiply("0.1", "0.1")  // "0.01"

Parameters:

  • a: string - First factor
  • b: string - Second factor

Returns: string - Product

Throws: On overflow


divide(a, b)

Divide a by b.

divide("100", "4")      // "25"
divide("10", "3")       // "3.333..."

Parameters:

  • a: string - Dividend
  • b: string - Divisor

Returns: string - Quotient

Throws: On division by zero


remainder(a, b)

Calculate remainder of a divided by b.

remainder("10", "3")    // "1"
remainder("7.5", "2")   // "1.5"

Comparison Operations

compare(a, b)

Compare two decimal values.

compare("100", "200")   // -1
compare("200", "200")   // 0
compare("300", "200")   // 1

Returns: number - -1 if a < b, 0 if equal, 1 if a > b


min(a, b) / max(a, b)

Return minimum or maximum of two values.

min("100", "200")       // "100"
max("100", "200")       // "200"

Rounding Operations

round(value, dp, mode)

Round to specified decimal places.

round("123.456", 2, "half_up")     // "123.46"
round("123.445", 2, "half_even")   // "123.44"

Parameters:

  • value: string - Value to round
  • dp: number - Decimal places (0-28)
  • mode: string - Rounding mode

Rounding Modes:

ModeDescription
"half_even" or "bankers"Ties round to nearest even
"half_up"Ties round away from zero
"half_down"Ties round toward zero
"up"Always toward +infinity
"down"Always toward -infinity
"toward_zero" or "truncate"Truncate
"away_from_zero"Away from zero

floor(value) / ceil(value) / trunc(value)

Convenience rounding functions.

floor("2.7")    // "2"
floor("-2.7")   // "-3"
ceil("2.3")     // "3"
ceil("-2.3")    // "-2"
trunc("2.9")    // "2"
trunc("-2.9")   // "-2"

Properties

abs(value)

Absolute value.

abs("-42")      // "42"
abs("42")       // "42"

is_zero(value) / is_positive(value) / is_negative(value)

Boolean checks.

is_zero("0.00")        // true
is_positive("5")       // true
is_negative("-5")      // true

scale(value)

Get the scale (decimal places).

scale("123.45")        // 2
scale("100")           // 0
scale("1.000")         // 3

normalize(value)

Remove trailing zeros.

normalize("100.00")    // "100"
normalize("1.50")      // "1.5"

Financial Functions

simple_interest(principal, rate, time)

Calculate simple interest.

simple_interest("10000", "0.05", "3")  // "1500"

compound_interest(principal, rate, periods_per_year, years)

Calculate compound interest.

compound_interest("10000", "0.05", 12, 5)  // Total interest

future_value(present_value, rate, periods)

Calculate future value.

future_value("10000", "0.07", 10)  // ~"19671.51"

present_value(future_value, rate, periods)

Calculate present value.

present_value("19672", "0.07", 10)  // ~"10000"

percentage_of(value, percent)

Calculate percentage of a value.

percentage_of("200", "0.15")  // "30" (15% of 200)

percentage_change(old_value, new_value)

Calculate percentage change.

percentage_change("100", "125")  // "0.25" (25% increase)

Risk Functions

health_factor(collateral, debt, threshold)

Calculate lending position health factor.

health_factor("10000", "5000", "0.80")  // "1.6"

is_healthy(collateral, debt, threshold)

Check if position is healthy (HF >= 1).

is_healthy("10000", "5000", "0.80")  // true

liquidation_price(collateral_amount, debt, threshold)

Calculate liquidation price.

liquidation_price("5", "10000", "0.80")  // "2500"

max_borrowable(collateral, threshold, min_health_factor)

Calculate maximum safe debt.

max_borrowable("100000", "0.80", "1.5")  // "53333.33..."

Tolerance Functions

approx_eq(a, b, tolerance)

Check if values are approximately equal.

approx_eq("100.00", "100.01", "0.02")  // true

within_percentage(a, b, percent)

Check if values are within percentage of each other.

within_percentage("102", "100", "5")  // true (within 5%)

within_basis_points(a, b, bps)

Check if values are within basis points.

within_basis_points("1.0010", "1.0000", "15")  // true (within 15 bps)

Constants

ZERO        // "0"
ONE         // "1"
ONE_HUNDRED // "100"
MAX         // Maximum representable value
MIN         // Minimum representable value

Error Types

All functions throw JavaScript errors on failure:

try {
  divide("1", "0");
} catch (e) {
  // e.message contains error description
  switch (true) {
    case e.message.includes("division by zero"):
      // Handle division by zero
      break;
    case e.message.includes("overflow"):
      // Handle overflow
      break;
    case e.message.includes("invalid"):
      // Handle parse error
      break;
  }
}

Stylus Integration

Keystone’s no_std core makes it compatible with Arbitrum Stylus smart contracts. This guide covers building and deploying Stylus contracts that use Keystone for precision arithmetic.

Prerequisites

Install Rust and WASM target

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown

Install cargo-stylus

cargo install cargo-stylus

Get testnet ETH

For deployment, you’ll need Arbitrum Sepolia ETH from a faucet.

Project Setup

Cargo.toml

[package]
name = "my-stylus-contract"
version = "0.1.0"
edition = "2021"

[workspace]

[dependencies]
precision-core = { version = "0.1", default-features = false }
stylus-sdk = { version = "=0.9.2", default-features = false, features = ["stylus-proc"] }
alloy-primitives = "=0.8.20"
ruint = ">=1.12.3, <1.17"

[dev-dependencies]
motsu = "0.10"

[features]
default = ["mini-alloc"]
mini-alloc = ["stylus-sdk/mini-alloc"]
export-abi = ["stylus-sdk/export-abi"]

[lib]
crate-type = ["lib", "cdylib"]

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true

Contract Structure

#![allow(unused)]
#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
#![cfg_attr(not(any(test, feature = "export-abi")), no_std)]
fn main() {
extern crate alloc;

use alloc::{vec, vec::Vec};
use alloy_primitives::U256;
use precision_core::{Decimal, RoundingMode};
use stylus_sdk::prelude::*;

sol_storage! {
    #[entrypoint]
    pub struct MyContract {
        uint256 some_value;
    }
}

#[public]
impl MyContract {
    pub fn my_function(&self, input: U256) -> Result<U256, Vec<u8>> {
        // Use Keystone precision arithmetic
        let decimal = u256_to_decimal(input);
        let result = decimal.checked_mul(Decimal::from(2i64))
            .ok_or_else(|| b"overflow".to_vec())?;
        Ok(decimal_to_u256(result))
    }
}
}

U256 Conversion

Stylus uses U256 for numbers, while Keystone uses Decimal. Convert between them:

#![allow(unused)]
fn main() {
const SCALE: u64 = 1_000_000_000_000_000_000; // 1e18

fn u256_to_decimal(value: U256) -> Decimal {
    let lo: u128 = value.as_limbs()[0] as u128
        | ((value.as_limbs()[1] as u128) << 64);
    let raw = Decimal::from(lo);
    raw.checked_div(Decimal::from(SCALE))
        .unwrap_or(Decimal::MAX)
}

fn decimal_to_u256(value: Decimal) -> U256 {
    let scaled = value
        .checked_mul(Decimal::from(SCALE))
        .unwrap_or(Decimal::MAX)
        .round(0, RoundingMode::TowardZero);
    let (mantissa, _scale) = scaled.to_parts();
    U256::from(mantissa.unsigned_abs())
}
}

Building

Check compilation

cargo stylus check

Build for release

cargo build --release --target wasm32-unknown-unknown

Export ABI

cargo stylus export-abi

This generates Solidity-compatible ABI for your contract.

Deployment

Deploy to Arbitrum Sepolia

cargo stylus deploy \
  --private-key-path=./key.txt \
  --endpoint=https://sepolia-rollup.arbitrum.io/rpc

The CLI will output your contract address after deployment.

Deploy to Arbitrum One (mainnet)

cargo stylus deploy \
  --private-key-path=./key.txt \
  --endpoint=https://arb1.arbitrum.io/rpc

Contract Interaction

Using Foundry Cast

Read-only call:

cast call <address> "myFunction(uint256)(uint256)" 1000000000000000000 \
  --rpc-url https://sepolia-rollup.arbitrum.io/rpc

Transaction:

cast send <address> "myFunction(uint256)" 1000000000000000000 \
  --private-key <key> \
  --rpc-url https://sepolia-rollup.arbitrum.io/rpc

Example Contracts

Keystone provides three reference implementations:

stylus-lending

Health factor, liquidation price, and max borrow calculations for lending protocols.

cd examples/stylus-lending
cargo stylus check

stylus-amm

Constant product AMM calculations: swap output, price impact, liquidity math.

cd examples/stylus-amm
cargo stylus check

stylus-vault

ERC4626-style vault calculations: share price, compound yield, APY conversion.

cd examples/stylus-vault
cargo stylus check

stylus-oracle

RedStone oracle integration: price-aware health factor, liquidation checks, TWAP.

cd examples/stylus-oracle
cargo stylus check

Testing with Motsu

Motsu provides unit testing for Stylus contracts without a blockchain runtime:

#![allow(unused)]
fn main() {
use motsu::prelude::*;
use alloy_primitives::{Address, U256};

#[motsu::test]
fn test_health_factor(contract: Contract<LendingPool>) {
    let alice = Address::random();

    contract.sender(alice).set_liquidation_threshold(U256::from(8000u64));

    let collateral = U256::from(10_000u64) * U256::from(10u128.pow(18));
    let debt = U256::from(5_000u64) * U256::from(10u128.pow(18));

    let hf = contract
        .sender(alice)
        .calculate_health_factor(collateral, debt)
        .expect("should calculate");

    assert!(hf > U256::from(10u128.pow(18))); // HF > 1.0
}
}

Run tests:

cargo test

Motsu requires stylus-sdk without the hostio-caching feature.

Best Practices

Error Handling

Use checked arithmetic and return errors as Vec<u8>:

#![allow(unused)]
fn main() {
let result = a.checked_mul(b)
    .ok_or_else(|| b"multiplication overflow".to_vec())?;
}

Precision

Keystone provides 28 significant digits. For most DeFi use cases, this exceeds requirements.

Gas Optimization

  • Use opt-level = "z" for smallest WASM size
  • Enable LTO and strip debug info
  • The 24KB compressed size limit requires efficient code

Rounding

Choose rounding modes appropriate for financial context:

  • HalfUp: Traditional rounding (0.5 rounds up)
  • HalfEven: Banker’s rounding (0.5 rounds to nearest even)
  • TowardZero: Truncation (always toward zero)
  • Down: Floor (always toward negative infinity)

Troubleshooting

Contract too large

If your contract exceeds 24KB:

  1. Remove unused dependencies
  2. Use #[inline(never)] on rarely-called functions
  3. Consider splitting into multiple contracts

Build failures

Ensure all dependencies support no_std:

precision-core = { version = "0.1", default-features = false }

Deployment fails

  1. Verify you have sufficient ETH for gas
  2. Check RPC endpoint is accessible
  3. Ensure private key file has correct format

Example Contracts

Keystone includes five Stylus example contracts demonstrating precision arithmetic for different DeFi use cases.

Deployed Contracts (Arbitrum One)

ContractAddressArbiscan
stylus-lending0x4dff9348275ac3c24e2d3abf54af61d3ebee1585View
stylus-amm0x9615cc2f65d8bbe4cdc80343db75a6ec32da93cdView
stylus-vault0xdaf8f1a5f8025210f07665d4ccf2d2c0622a41faView

stylus-lending

Location: examples/stylus-lending/

Lending protocol calculations using Keystone for deterministic risk assessment.

Functions

FunctionDescription
calculate_health_factor(Collateral × Threshold) / Debt
calculate_liquidation_priceDebt / (Collateral × Threshold)
calculate_max_borrowMaximum borrowable given collateral
is_liquidatableCheck if health factor < 1
calculate_liquidation_amountsDebt coverage with liquidation bonus

Example Usage

#![allow(unused)]
fn main() {
// Health factor calculation
// Collateral: $10,000, Debt: $5,000, Threshold: 80%
// HF = (10000 * 0.8) / 5000 = 1.6

let hf = contract.calculate_health_factor(
    U256::from(10000) * U256::from(10).pow(U256::from(18)), // collateral
    U256::from(5000) * U256::from(10).pow(U256::from(18)),  // debt
)?;
// Returns 1.6e18
}

Target Protocols

  • Aave-style lending markets
  • Radiant, Lodestar on Arbitrum
  • Any protocol using health factor for liquidation

stylus-amm

Location: examples/stylus-amm/

Constant product AMM (x*y=k) calculations for decentralized exchanges.

Functions

FunctionDescription
calculate_swap_outputOutput amount for given input
calculate_price_impactPrice impact as percentage
calculate_swap_inputRequired input for desired output
calculate_spot_priceCurrent pool price
calculate_liquidity_mintLP tokens for deposit
calculate_liquidity_burnAssets returned for LP redemption

Example Usage

#![allow(unused)]
fn main() {
// Swap calculation with 0.3% fee
// Pool: 1000 ETH / 2,000,000 USDC
// Swap 10 ETH for USDC

contract.set_fee(U256::from(30)); // 30 bps = 0.3%

let output = contract.calculate_swap_output(
    U256::from(1000) * SCALE,     // reserve_in (ETH)
    U256::from(2000000) * SCALE,  // reserve_out (USDC)
    U256::from(10) * SCALE,       // amount_in
)?;
// Returns ~19,841 USDC (with fee and slippage)
}

Target Protocols

  • Uniswap V2-style AMMs
  • Camelot, Trader Joe on Arbitrum
  • Any constant product DEX

stylus-vault

Location: examples/stylus-vault/

ERC4626-compatible vault calculations for yield aggregators.

Functions

FunctionDescription
calculate_shares_for_depositShares to mint for deposit
calculate_assets_for_redeemAssets returned for share redemption
calculate_share_priceCurrent price per share
calculate_compound_yieldCompounded return over periods
calculate_apy_from_aprConvert APR to APY
calculate_performance_feeFee on gains
calculate_management_feeTime-based fee
calculate_net_asset_valueNAV per share

Example Usage

#![allow(unused)]
fn main() {
// Calculate shares for $1000 deposit
// Vault has $100,000 total assets, 95,000 shares outstanding

let shares = contract.calculate_shares_for_deposit(
    U256::from(1000) * SCALE,    // deposit amount
    U256::from(100000) * SCALE,  // total assets
    U256::from(95000) * SCALE,   // total supply
)?;
// Returns 950 shares (share price = 100000/95000 ≈ 1.0526)
}
#![allow(unused)]
fn main() {
// Calculate APY from 5% APR with daily compounding

let apy_bps = contract.calculate_apy_from_apr(
    U256::from(500) * SCALE,  // 500 bps = 5% APR
    U256::from(365),          // daily compounding
)?;
// Returns ~512.67 bps = 5.1267% APY
}

Target Protocols

  • ERC4626 vaults
  • Yearn-style yield aggregators
  • Pendle, Jones DAO on Arbitrum

stylus-options

Location: examples/stylus-options/

On-chain Black-Scholes options pricing with 128-bit decimal precision.

Functions

FunctionDescription
price_callEuropean call option price (Black-Scholes)
price_putEuropean put option price
call_option_greeksDelta, Gamma, Theta, Vega, Rho for calls
put_option_greeksGreeks for puts
calculate_ivImplied volatility from market price
put_call_parity_checkVerify C - P = S - Ke^(-rT)

Target Protocols

  • Dopex (Arbitrum options)
  • Rysk Finance (options AMM)
  • Any protocol requiring on-chain exp(), ln(), sqrt() for BSM pricing

stylus-oracle

Location: examples/stylus-oracle/

RedStone oracle integration for price-aware DeFi calculations.

Functions

FunctionDescription
calculate_health_factor_with_pricesHealth factor using live oracle prices
calculate_liquidation_price_with_oracleOracle-based liquidation trigger
calculate_max_borrow_with_pricesMax borrow with real-time pricing
is_liquidatable_with_pricesLiquidation status with live data
calculate_twapTime-weighted average price
calculate_price_deviationAnomaly detection vs median

Target Protocols

  • Any DeFi protocol using RedStone pull-based oracles on Arbitrum
  • Cross-VM interoperability between WASM and EVM contracts

Building Examples

# Build all examples
cd examples/stylus-lending && cargo build --release
cd ../stylus-amm && cargo build --release
cd ../stylus-vault && cargo build --release

Deploying Examples

cd examples/stylus-lending
cargo stylus deploy \
  --private-key-path=../../key.txt \
  --endpoint=https://sepolia-rollup.arbitrum.io/rpc

Replace with your private key path and desired network.

keystone-defi SDK

Unified DeFi computation SDK combining all Keystone modules.

Installation

[dependencies]
keystone-defi = "0.1.0-alpha.3"

Overview

keystone-defi provides a single integration point for DeFi protocols:

ModuleUse Case
precision128-bit decimal arithmetic
lendingHealth factor, liquidation, collateral
ammSwaps, liquidity, price impact
vaultERC4626 shares, compounding, APY
derivativesPerpetuals, funding, margin
optionsBlack-Scholes, Greeks

Quick Start

Use the prelude for common imports:

#![allow(unused)]
fn main() {
use keystone_defi::prelude::*;
use core::str::FromStr;

// Lending: Health factor
let hf = health_factor(
    Decimal::from_str("10000")?,  // collateral
    Decimal::from_str("5000")?,   // debt
    Decimal::from_str("0.8")?,    // threshold
)?;

// AMM: Swap output
let output = calculate_swap_output(
    Decimal::from(1_000_000i64),
    Decimal::from(1_000_000i64),
    Decimal::from(1000i64),
    Decimal::from(30i64),
)?;

// Vault: Share price
let price = calculate_share_price(
    Decimal::from(1_050_000i64),  // total assets
    Decimal::from(1_000_000i64),  // total supply
)?;

// Derivatives: PnL
let position = PerpPosition {
    size: Decimal::from_str("1.5")?,
    entry_price: Decimal::from(2000i64),
    is_long: true,
    leverage: Decimal::from(10i64),
    collateral: Decimal::from(300i64),
};
let pnl = calculate_pnl(&position, Decimal::from(2200i64))?;
}

Modules

precision

Core decimal type and arithmetic:

#![allow(unused)]
fn main() {
use keystone_defi::precision::{Decimal, RoundingMode, ArithmeticError};

let a = Decimal::from_str("123.456")?;
let b = Decimal::from_str("789.012")?;
let sum = a.try_add(b)?;
}

lending

Risk metrics for lending protocols:

#![allow(unused)]
fn main() {
use keystone_defi::lending::*;

let hf = health_factor(collateral, debt, threshold)?;
let liq = liquidation_price(collateral, debt, price, threshold)?;
let max = max_borrowable(collateral, threshold, current_debt)?;
let healthy = is_healthy(collateral, debt, threshold)?;
}

amm

DEX and AMM calculations:

#![allow(unused)]
fn main() {
use keystone_defi::amm::*;

// Constant product
let out = calculate_swap_output(reserve_in, reserve_out, amount_in, fee_bps)?;
let impact = calculate_price_impact(reserve_in, reserve_out, amount_in)?;

// Concentrated liquidity
let sqrt_price = tick_to_sqrt_price(tick)?;
let liquidity = calculate_liquidity_from_amounts(...)?;
let il = calculate_impermanent_loss(...)?;
}

vault

Yield vault calculations:

#![allow(unused)]
fn main() {
use keystone_defi::vault::*;

let shares = calculate_shares_for_deposit(assets, total_assets, total_supply)?;
let assets = calculate_assets_for_redeem(shares, total_assets, total_supply)?;
let price = calculate_share_price(total_assets, total_supply)?;
let apy = calculate_apy_from_apr(apr, 365)?; // Daily compounding
let fee = calculate_performance_fee(gains, fee_bps)?;
}

derivatives

Perpetual futures:

#![allow(unused)]
fn main() {
use keystone_defi::derivatives::*;

let pnl = calculate_pnl(&position, current_price)?;
let liq = calculate_liquidation_price(&position, maintenance_rate)?;
let funding = calculate_funding_rate(&funding_params)?;
let leverage = calculate_effective_leverage(&position, current_price)?;
}

options

Options pricing:

#![allow(unused)]
fn main() {
use keystone_defi::options::*;

let call = black_scholes_call(&params)?;
let put = black_scholes_put(&params)?;
let greeks = call_greeks(&params)?;
let iv = implied_volatility(market_price, &params, true, None, None)?;
}

Stylus Integration

All types are no_std compatible:

#![allow(unused)]
#![cfg_attr(not(feature = "export-abi"), no_main, no_std)]
fn main() {
use keystone_defi::prelude::*;
use stylus_sdk::prelude::*;

#[public]
impl MyContract {
    pub fn health_check(&self, collateral: U256, debt: U256) -> Result<U256, Vec<u8>> {
        let c = u256_to_decimal(collateral);
        let d = u256_to_decimal(debt);
        let threshold = Decimal::from_str("0.8").unwrap();

        let hf = health_factor(c, d, threshold)
            .map_err(|_| b"calc error".to_vec())?;

        Ok(decimal_to_u256(hf))
    }
}
}

Feature Flags

[dependencies]
keystone-defi = { version = "0.1.0-alpha.3", default-features = false }

# Enable std library
keystone-defi = { version = "0.1.0-alpha.3", features = ["std"] }

Benchmarks

Performance characteristics of Keystone operations measured with Criterion.

Arithmetic Operations

OperationTime (ns)Throughput
Addition~8125M ops/sec
Subtraction~8125M ops/sec
Multiplication~8125M ops/sec
Division~3628M ops/sec
Remainder~4025M ops/sec

Rounding Operations

OperationTime (ns)
round_dp (2 places)~15
floor~12
ceil~12
trunc~10

Financial Calculations

OperationTime (ns)
simple_interest~25
compound_interest (12 periods, 5 years)~850
future_value (10 periods)~200
present_value (10 periods)~220
effective_annual_rate~180

Risk Metrics

OperationTime (ns)
health_factor~45
liquidation_price~50
collateral_ratio~40
max_borrowable~55

precision-core vs rust_decimal

Measured with Criterion.rs on identical hardware. precision-core wraps rust_decimal with checked arithmetic, overflow detection, and formal verification boundaries.

Operationprecision-core (ns)rust_decimal (ns)Overhead
Addition7.37.0~4%
Subtraction8.07.5~7%
Multiplication8.77.7~13%
Division32.832.4~1%
mul_div (compound)22.720.4~11%
Compound interest (12×)127.0105.0~21%
Health factor (mul+div)23.421.2~10%
Swap output (3 ops)73.869.9~6%
Large value mul16.716.1~4%
Large value div70.769.4~2%

The wrapper overhead ranges from 1-13% for individual operations and 6-21% for compound operations. This cost buys:

  • Checked arithmetic that returns Option instead of panicking
  • Formal verification via Kani proof harnesses
  • Consistent error types (ArithmeticError)
  • DeFi-specific operations (health factor, swap math)

Run the comparison benchmark:

cargo bench --package precision-core --bench comparison

Running Benchmarks

cd keystone
cargo bench

Specific Benchmark

cargo bench --bench arithmetic
cargo bench --bench financial
cargo bench --bench risk

With Baseline Comparison

# Save baseline
cargo bench -- --save-baseline main

# Compare to baseline
cargo bench -- --baseline main

Benchmark Code

Located in crates/precision-core/benches/:

#![allow(unused)]
fn main() {
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use precision_core::Decimal;

fn bench_addition(c: &mut Criterion) {
    let a = Decimal::from(12345i64);
    let b = Decimal::from(67890i64);

    c.bench_function("decimal_add", |bencher| {
        bencher.iter(|| black_box(a).checked_add(black_box(b)))
    });
}

criterion_group!(benches, bench_addition);
criterion_main!(benches);
}

Stylus Gas Benchmarks

Keystone Stylus contracts deployed on Arbitrum One.

Deployed Contracts

ContractAddressSize
stylus-lending0x4dff9348275ac3c24e2d3abf54af61d3ebee158512.2 KB
stylus-amm0x9615cc2f65d8bbe4cdc80343db75a6ec32da93cd16.9 KB
stylus-vault0xdaf8f1a5f8025210f07665d4ccf2d2c0622a41fa14.4 KB

Measured Gas Usage (Arbitrum One)

ContractFunctionGas
stylus-lendingcalculateHealthFactor59,853
stylus-lendingisLiquidatable59,851
stylus-ammcalculateSpotPrice60,026
stylus-ammcalculateSwapOutput62,651
stylus-ammcalculateLiquidityMint61,142
stylus-vaultcalculateSharePrice59,082
stylus-vaultcalculateSharesForDeposit59,396
stylus-vaultcalculateAssetsForRedeem59,452
stylus-vaultcalculateCompoundYield (30 periods)59,904
stylus-vaultcalculateApyFromApr (365 compounds)75,656
stylus-vaultcalculatePerformanceFee60,799
stylus-vaultcalculateManagementFee61,122

Gas Comparison: Stylus vs Solidity (Measured)

Solidity benchmark contract: 0x41d4f095Da18Fd25c28CDbE0532a6fb730bbB9CF

OperationStylus (gas)Solidity (gas)Winner
Share price (1 div)58,74222,606Solidity 62% cheaper
Shares for deposit (2 ops)59,00522,898Solidity 61% cheaper
Compound yield (30 loops)59,51333,205Solidity 44% cheaper
APY from APR (365 loops)75,316148,881Stylus 49% cheaper

Key Insight

Simple arithmetic: Solidity wins due to lower base overhead.

Loop-heavy computation: Stylus wins significantly. As iterations increase, Stylus becomes more efficient because WASM opcodes cost ~100x less than EVM opcodes.

When to Use Stylus

Use Keystone + Stylus when:

  • Calculations involve many iterations (compound interest, Monte Carlo)
  • 28-digit precision is required (vs Solidity’s 18-digit practical limit)
  • Cross-platform determinism matters (same results everywhere)

Use Solidity when:

  • Simple arithmetic only
  • Gas optimization is critical for single operations
  • Existing Solidity codebase

Break-Even Analysis

Based on measured data, Stylus becomes cheaper at approximately 100+ loop iterations per call.

Why Stylus is Cheaper

  1. WASM Execution: WASM opcodes cost ~1/100th of EVM opcodes
  2. No Storage Overhead: Pure computation functions avoid SLOAD/SSTORE
  3. Efficient Loops: Iteration-heavy calculations (compound interest) benefit most
  4. Native Integer Ops: 128-bit arithmetic is native in WASM

Running Gas Benchmarks

Install Foundry:

curl -L https://foundry.paradigm.xyz | bash
foundryup

Estimate gas for a call:

cast estimate 0x4dff9348275ac3c24e2d3abf54af61d3ebee1585 \
  "calculateHealthFactor(uint256,uint256)(uint256)" \
  10000000000000000000000 5000000000000000000000 \
  --rpc-url https://arb1.arbitrum.io/rpc

Activation Costs

One-time costs paid at deployment:

ContractActivation Fee
stylus-lending0.000090 ETH
stylus-amm0.000103 ETH
stylus-vault0.000099 ETH

After activation, calls use standard Arbitrum gas pricing.


WASM Performance

WASM operations include JS-WASM boundary overhead:

OperationNative (ns)WASM (ns)Overhead
Addition8~50~6x
Division36~100~3x
compound_interest850~1200~1.4x

WASM Binary Size

BuildSize
Debug~450 KB
Release~97 KB
Release + wasm-opt~85 KB

Optimization Settings

From Cargo.toml:

[profile.release]
opt-level = "z"      # Optimize for size
lto = "fat"          # Full link-time optimization
codegen-units = 1    # Better optimization
panic = "abort"      # Smaller binary
strip = true         # Remove symbols
overflow-checks = true  # Keep overflow checking

Memory Usage

TypeSize
Decimal16 bytes
RoundingMode1 byte
ArithmeticError1 byte

Stack-only allocation for all operations.

Comparison with Alternatives

vs JavaScript Number

// JavaScript floating-point
0.1 + 0.2  // 0.30000000000000004

// Keystone
keystone.add("0.1", "0.2")  // "0.3"

Keystone is slower but guarantees correctness for financial calculations.

vs BigInt

JavaScript BigInt handles integers only. Keystone provides:

  • Decimal precision up to 28 digits
  • Native rounding modes
  • Financial functions

vs decimal.js

Similar precision, but Keystone provides:

  • Deterministic cross-platform results
  • Smaller WASM bundle
  • ZK-proof compatibility
  • Rust native performance

Profiling

CPU Profiling

cargo bench --bench arithmetic -- --profile-time 10

Memory Profiling

cargo bench --bench arithmetic -- --plotting-backend disabled

CI Benchmarks

Benchmarks run on every PR to detect regressions:

- name: Run benchmarks
  run: cargo bench -- --noplot

- name: Compare with main
  run: |
    cargo bench -- --save-baseline pr
    git checkout main
    cargo bench -- --baseline pr

Determinism

Keystone guarantees identical results across all platforms and execution environments.

Why Determinism Matters

Financial Applications

  • Audit trails require reproducible calculations
  • Reconciliation between systems
  • Regulatory compliance

Zero-Knowledge Proofs

  • Prover and verifier must compute identical results
  • Any divergence breaks proof validity
  • Cross-platform proof generation

Distributed Systems

  • Consensus requires identical state transitions
  • Smart contract execution
  • Multi-party computation

Guarantees

Bit-Exact Results

The same inputs always produce the same outputs, regardless of:

  • Operating system (Linux, macOS, Windows)
  • CPU architecture (x86_64, ARM64, WASM)
  • Compiler version
  • Optimization level

No Floating-Point

Keystone uses fixed-point decimal arithmetic:

#![allow(unused)]
fn main() {
// Floating-point: non-deterministic
let a: f64 = 0.1;
let b: f64 = 0.2;
let c = a + b;  // May vary by platform

// Keystone: deterministic
let a = Decimal::new(1, 1);  // 0.1
let b = Decimal::new(2, 1);  // 0.2
let c = a.checked_add(b);    // Always 0.3
}

no_std Core

The core library has no OS dependencies:

#![allow(unused)]
#![no_std]  // No standard library
#![forbid(unsafe_code)]  // No undefined behavior
fn main() {
}

Verification

Test Vectors

Pre-computed test vectors verify determinism:

#![allow(unused)]
fn main() {
#[test]
fn test_determinism_vectors() {
    let vectors = [
        ("100", "3", "div", "33.333333333333333333333333333"),
        ("0.1", "0.2", "add", "0.3"),
        ("999999999999999999999999999", "2", "mul",
         "1999999999999999999999999998"),
    ];

    for (a, b, op, expected) in vectors {
        let a: Decimal = a.parse().unwrap();
        let b: Decimal = b.parse().unwrap();
        let result = match op {
            "add" => a.checked_add(b),
            "mul" => a.checked_mul(b),
            "div" => a.checked_div(b),
            _ => panic!("unknown op"),
        };
        assert_eq!(result.unwrap().to_string(), expected);
    }
}
}

Cross-Platform CI

CI runs on multiple platforms:

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    target:
      - x86_64-unknown-linux-gnu
      - x86_64-pc-windows-msvc
      - x86_64-apple-darwin
      - aarch64-apple-darwin
      - wasm32-unknown-unknown

Property-Based Testing

Random inputs verify consistency:

#![allow(unused)]
fn main() {
use proptest::prelude::*;

proptest! {
    #[test]
    fn add_commutative(a: i64, b: i64) {
        let da = Decimal::from(a);
        let db = Decimal::from(b);

        let ab = da.checked_add(db);
        let ba = db.checked_add(da);

        assert_eq!(ab, ba);
    }
}
}

ZK Compatibility

SP1 Integration

Keystone is designed for use in SP1 zkVM:

// In SP1 program
#![no_main]
sp1_zkvm::entrypoint!(main);

use precision_core::Decimal;

pub fn main() {
    let a: Decimal = sp1_zkvm::io::read();
    let b: Decimal = sp1_zkvm::io::read();

    let result = a.checked_mul(b).expect("overflow");

    sp1_zkvm::io::commit(&result);
}

Proof Generation

#![allow(unused)]
fn main() {
// Generate proof
let (pk, vk) = client.setup(ELF);
let mut stdin = SP1Stdin::new();
stdin.write(&a);
stdin.write(&b);

let proof = client.prove(&pk, stdin)?;

// Verify anywhere
client.verify(&proof, &vk)?;
}

Implementation Details

Decimal Representation

128-bit fixed-point with explicit scale:

#![allow(unused)]
fn main() {
struct Decimal {
    // 96-bit mantissa + 1-bit sign
    mantissa: i128,
    // Scale: number of decimal places (0-28)
    scale: u32,
}
}

Normalization

Canonical form ensures comparison stability:

#![allow(unused)]
fn main() {
let a = Decimal::new(100, 2);   // 1.00
let b = Decimal::new(1, 0);     // 1

// Internal comparison normalizes
assert!(a == b);

// Explicit normalization
let normalized = a.normalize(); // 1 (scale = 0)
}

Rounding Specification

Each rounding mode has precise semantics:

ModeTie-Breaking Rule
HalfEvenTo nearest even digit
HalfUpAway from zero
HalfDownToward zero

Edge Cases

Maximum Precision

28 significant digits:

#![allow(unused)]
fn main() {
let max_precision = Decimal::new(
    9999999999999999999999999999i128,
    0
);
}

Minimum Value

#![allow(unused)]
fn main() {
let min_scale = Decimal::new(1, 28);
// 0.0000000000000000000000000001
}

Overflow Behavior

Explicit error handling, never silent wraparound:

#![allow(unused)]
fn main() {
let max = Decimal::MAX;
let result = max.checked_add(Decimal::ONE);
assert!(result.is_none());  // Never silently wraps
}