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% |
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;
}
}
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 |
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);
}
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
}