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
| Crate | Description |
|---|---|
precision-core | Decimal type with deterministic arithmetic |
financial-calc | Interest, TVM, and percentage calculations |
risk-metrics | DeFi health factors and liquidation logic |
wasm-bindings | JavaScript/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:
| Operation | Time |
|---|---|
| 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
| Mode | Description | 2.5 → | 3.5 → | -2.5 → |
|---|---|---|---|---|
HalfEven | Banker’s rounding (ties to even) | 2 | 4 | -2 |
HalfUp | Ties round away from zero | 3 | 4 | -3 |
HalfDown | Ties round toward zero | 2 | 3 | -2 |
Up | Always round toward +∞ | 3 | 4 | -2 |
Down | Always round toward -∞ | 2 | 3 | -3 |
TowardZero | Truncate (round toward zero) | 2 | 3 | -2 |
AwayFromZero | Round away from zero | 3 | 4 | -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 Case | Recommended Mode |
|---|---|
| Invoice totals | HalfUp |
| Tax calculations | HalfUp or Up (toward government) |
| Interest accumulation | HalfEven |
| Currency display | HalfUp |
| 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
| Scenario | Recommended Function |
|---|---|
| Comparing prices | within_basis_points |
| Verifying calculations | approx_eq with small absolute tolerance |
| Comparing large values | approx_eq_relative |
| Test assertions | approx_eq_ulps (handles edge cases) |
| Rate comparisons | within_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
| Frequency | Periods per Year |
|---|---|
| Annual | 1 |
| Semi-annual | 2 |
| Quarterly | 4 |
| Monthly | 12 |
| Daily | 365 |
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
| NPV | Decision |
|---|---|
| > 0 | Accept (creates value) |
| = 0 | Indifferent |
| < 0 | Reject (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 Points | Percentage |
|---|---|
| 1 bp | 0.01% |
| 10 bp | 0.10% |
| 25 bp | 0.25% |
| 50 bp | 0.50% |
| 100 bp | 1.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(¶ms)?;
}
Greeks
Calculate all Greeks for risk management:
#![allow(unused)]
fn main() {
use financial_calc::options::{call_greeks, put_greeks};
let greeks = call_greeks(¶ms)?;
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,
¶ms,
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(¶ms)?;
// 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 Factor | Status |
|---|---|
| > 1.0 | Safe |
| = 1.0 | At liquidation threshold |
| < 1.0 | Liquidatable |
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:
| Protocol | Typical 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
- Health factor drops below 1.0
- Liquidator repays portion of debt
- Liquidator receives collateral + bonus
- 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
- Batch operations: Minimize JS-WASM boundary crossings
- Reuse results: Store intermediate values in JS when possible
- 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 operandb: 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- Minuendb: 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 factorb: 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- Dividendb: 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 rounddp: number- Decimal places (0-28)mode: string- Rounding mode
Rounding Modes:
| Mode | Description |
|---|---|
"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:
- Remove unused dependencies
- Use
#[inline(never)]on rarely-called functions - Consider splitting into multiple contracts
Build failures
Ensure all dependencies support no_std:
precision-core = { version = "0.1", default-features = false }
Deployment fails
- Verify you have sufficient ETH for gas
- Check RPC endpoint is accessible
- 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)
| Contract | Address | Arbiscan |
|---|---|---|
| stylus-lending | 0x4dff9348275ac3c24e2d3abf54af61d3ebee1585 | View |
| stylus-amm | 0x9615cc2f65d8bbe4cdc80343db75a6ec32da93cd | View |
| stylus-vault | 0xdaf8f1a5f8025210f07665d4ccf2d2c0622a41fa | View |
stylus-lending
Location: examples/stylus-lending/
Lending protocol calculations using Keystone for deterministic risk assessment.
Functions
| Function | Description |
|---|---|
calculate_health_factor | (Collateral × Threshold) / Debt |
calculate_liquidation_price | Debt / (Collateral × Threshold) |
calculate_max_borrow | Maximum borrowable given collateral |
is_liquidatable | Check if health factor < 1 |
calculate_liquidation_amounts | Debt 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
| Function | Description |
|---|---|
calculate_swap_output | Output amount for given input |
calculate_price_impact | Price impact as percentage |
calculate_swap_input | Required input for desired output |
calculate_spot_price | Current pool price |
calculate_liquidity_mint | LP tokens for deposit |
calculate_liquidity_burn | Assets 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
| Function | Description |
|---|---|
calculate_shares_for_deposit | Shares to mint for deposit |
calculate_assets_for_redeem | Assets returned for share redemption |
calculate_share_price | Current price per share |
calculate_compound_yield | Compounded return over periods |
calculate_apy_from_apr | Convert APR to APY |
calculate_performance_fee | Fee on gains |
calculate_management_fee | Time-based fee |
calculate_net_asset_value | NAV 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
| Function | Description |
|---|---|
price_call | European call option price (Black-Scholes) |
price_put | European put option price |
call_option_greeks | Delta, Gamma, Theta, Vega, Rho for calls |
put_option_greeks | Greeks for puts |
calculate_iv | Implied volatility from market price |
put_call_parity_check | Verify 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
| Function | Description |
|---|---|
calculate_health_factor_with_prices | Health factor using live oracle prices |
calculate_liquidation_price_with_oracle | Oracle-based liquidation trigger |
calculate_max_borrow_with_prices | Max borrow with real-time pricing |
is_liquidatable_with_prices | Liquidation status with live data |
calculate_twap | Time-weighted average price |
calculate_price_deviation | Anomaly 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:
| Module | Use Case |
|---|---|
precision | 128-bit decimal arithmetic |
lending | Health factor, liquidation, collateral |
amm | Swaps, liquidity, price impact |
vault | ERC4626 shares, compounding, APY |
derivatives | Perpetuals, funding, margin |
options | Black-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(¶ms)?;
let put = black_scholes_put(¶ms)?;
let greeks = call_greeks(¶ms)?;
let iv = implied_volatility(market_price, ¶ms, 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
| Operation | Time (ns) | Throughput |
|---|---|---|
| Addition | ~8 | 125M ops/sec |
| Subtraction | ~8 | 125M ops/sec |
| Multiplication | ~8 | 125M ops/sec |
| Division | ~36 | 28M ops/sec |
| Remainder | ~40 | 25M ops/sec |
Rounding Operations
| Operation | Time (ns) |
|---|---|
| round_dp (2 places) | ~15 |
| floor | ~12 |
| ceil | ~12 |
| trunc | ~10 |
Financial Calculations
| Operation | Time (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
| Operation | Time (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.
| Operation | precision-core (ns) | rust_decimal (ns) | Overhead |
|---|---|---|---|
| Addition | 7.3 | 7.0 | ~4% |
| Subtraction | 8.0 | 7.5 | ~7% |
| Multiplication | 8.7 | 7.7 | ~13% |
| Division | 32.8 | 32.4 | ~1% |
| mul_div (compound) | 22.7 | 20.4 | ~11% |
| Compound interest (12×) | 127.0 | 105.0 | ~21% |
| Health factor (mul+div) | 23.4 | 21.2 | ~10% |
| Swap output (3 ops) | 73.8 | 69.9 | ~6% |
| Large value mul | 16.7 | 16.1 | ~4% |
| Large value div | 70.7 | 69.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
Optioninstead 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
| Contract | Address | Size |
|---|---|---|
| stylus-lending | 0x4dff9348275ac3c24e2d3abf54af61d3ebee1585 | 12.2 KB |
| stylus-amm | 0x9615cc2f65d8bbe4cdc80343db75a6ec32da93cd | 16.9 KB |
| stylus-vault | 0xdaf8f1a5f8025210f07665d4ccf2d2c0622a41fa | 14.4 KB |
Measured Gas Usage (Arbitrum One)
| Contract | Function | Gas |
|---|---|---|
| stylus-lending | calculateHealthFactor | 59,853 |
| stylus-lending | isLiquidatable | 59,851 |
| stylus-amm | calculateSpotPrice | 60,026 |
| stylus-amm | calculateSwapOutput | 62,651 |
| stylus-amm | calculateLiquidityMint | 61,142 |
| stylus-vault | calculateSharePrice | 59,082 |
| stylus-vault | calculateSharesForDeposit | 59,396 |
| stylus-vault | calculateAssetsForRedeem | 59,452 |
| stylus-vault | calculateCompoundYield (30 periods) | 59,904 |
| stylus-vault | calculateApyFromApr (365 compounds) | 75,656 |
| stylus-vault | calculatePerformanceFee | 60,799 |
| stylus-vault | calculateManagementFee | 61,122 |
Gas Comparison: Stylus vs Solidity (Measured)
Solidity benchmark contract: 0x41d4f095Da18Fd25c28CDbE0532a6fb730bbB9CF
| Operation | Stylus (gas) | Solidity (gas) | Winner |
|---|---|---|---|
| Share price (1 div) | 58,742 | 22,606 | Solidity 62% cheaper |
| Shares for deposit (2 ops) | 59,005 | 22,898 | Solidity 61% cheaper |
| Compound yield (30 loops) | 59,513 | 33,205 | Solidity 44% cheaper |
| APY from APR (365 loops) | 75,316 | 148,881 | Stylus 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
- WASM Execution: WASM opcodes cost ~1/100th of EVM opcodes
- No Storage Overhead: Pure computation functions avoid SLOAD/SSTORE
- Efficient Loops: Iteration-heavy calculations (compound interest) benefit most
- 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:
| Contract | Activation Fee |
|---|---|
| stylus-lending | 0.000090 ETH |
| stylus-amm | 0.000103 ETH |
| stylus-vault | 0.000099 ETH |
After activation, calls use standard Arbitrum gas pricing.
WASM Performance
WASM operations include JS-WASM boundary overhead:
| Operation | Native (ns) | WASM (ns) | Overhead |
|---|---|---|---|
| Addition | 8 | ~50 | ~6x |
| Division | 36 | ~100 | ~3x |
| compound_interest | 850 | ~1200 | ~1.4x |
WASM Binary Size
| Build | Size |
|---|---|
| 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
| Type | Size |
|---|---|
| Decimal | 16 bytes |
| RoundingMode | 1 byte |
| ArithmeticError | 1 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:
| Mode | Tie-Breaking Rule |
|---|---|
| HalfEven | To nearest even digit |
| HalfUp | Away from zero |
| HalfDown | Toward 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
}