Skip to main content

keystone_defi/
lib.rs

1//! Keystone DeFi SDK - Unified computation library for Arbitrum protocols.
2//!
3//! This crate provides a single integration point for DeFi protocols on Arbitrum,
4//! combining precision arithmetic, financial calculations, and risk metrics.
5//!
6//! # Quick Start
7//!
8//! ```rust
9//! use keystone_defi::prelude::*;
10//! use core::str::FromStr;
11//!
12//! // Lending: Calculate health factor
13//! let collateral = Decimal::from_str("10000").unwrap();
14//! let debt = Decimal::from_str("5000").unwrap();
15//! let threshold = Decimal::from_str("0.8").unwrap();
16//! let health = health_factor(collateral, debt, threshold).unwrap();
17//!
18//! // AMM: Calculate swap output
19//! let output = calculate_swap_output(
20//!     Decimal::from(1000000i64),
21//!     Decimal::from(1000000i64),
22//!     Decimal::from(1000i64),
23//!     Decimal::from(30i64),  // 0.3% fee
24//! ).unwrap();
25//!
26//! // Vault: Calculate share price
27//! let share_price = calculate_share_price(
28//!     Decimal::from(1000000i64),
29//!     Decimal::from(950000i64),
30//! ).unwrap();
31//!
32//! // Derivatives: Calculate liquidation price
33//! let position = PerpPosition {
34//!     size: Decimal::from_str("1.5").unwrap(),
35//!     entry_price: Decimal::from(2000i64),
36//!     is_long: true,
37//!     leverage: Decimal::from(10i64),
38//!     collateral: Decimal::from(300i64),
39//! };
40//! let liq_price = calculate_liquidation_price(&position, Decimal::from_str("0.01").unwrap()).unwrap();
41//! ```
42//!
43//! # Modules
44//!
45//! - [`precision`] - Core decimal arithmetic with 28-digit precision
46//! - [`lending`] - Health factor, liquidation, and borrow calculations
47//! - [`amm`] - Swap, liquidity, and price impact calculations
48//! - [`vault`] - ERC4626 share/asset calculations and compounding
49//! - [`derivatives`] - Perpetual futures, funding rates, and margin calculations
50//! - [`options`] - Black-Scholes pricing and Greeks
51//!
52//! # Stylus Integration
53//!
54//! All types are `no_std` compatible and work in Arbitrum Stylus smart contracts:
55//!
56//! ```rust,ignore
57//! #![cfg_attr(not(feature = "export-abi"), no_main, no_std)]
58//! use keystone_defi::prelude::*;
59//! use stylus_sdk::prelude::*;
60//!
61//! #[public]
62//! impl MyContract {
63//!     pub fn calculate_health(&self, collateral: U256, debt: U256) -> Result<U256, Vec<u8>> {
64//!         let c = u256_to_decimal(collateral);
65//!         let d = u256_to_decimal(debt);
66//!         let threshold = Decimal::from_str("0.8").unwrap();
67//!         let hf = health_factor(c, d, threshold).map_err(|_| b"calc error".to_vec())?;
68//!         Ok(decimal_to_u256(hf))
69//!     }
70//! }
71//! ```
72
73#![no_std]
74#![forbid(unsafe_code)]
75#![deny(missing_docs)]
76
77/// Core precision arithmetic.
78pub mod precision {
79    pub use precision_core::{ArithmeticError, Decimal, ParseError, RoundingMode};
80}
81
82/// Lending protocol calculations.
83pub mod lending {
84    pub use risk_metrics::{
85        collateral_ratio, health_factor, is_healthy, liquidation_price, liquidation_threshold,
86        max_borrowable, available_liquidity, loan_to_value, utilization_rate,
87    };
88}
89
90/// AMM and DEX calculations.
91pub mod amm {
92    pub use financial_calc::amm::{
93        calculate_amounts_from_liquidity, calculate_impermanent_loss, calculate_liquidity_burn,
94        calculate_liquidity_from_amounts, calculate_liquidity_mint, calculate_position_value,
95        calculate_price_impact, calculate_spot_price, calculate_swap_input, calculate_swap_output,
96        sqrt_price_to_tick, tick_spacing_to_fee_bps, tick_to_sqrt_price, ConcentratedPosition,
97        MAX_TICK, MIN_TICK, TICK_SPACING_HIGH, TICK_SPACING_LOW, TICK_SPACING_MEDIUM,
98    };
99}
100
101/// Vault and yield calculations.
102pub mod vault {
103    pub use financial_calc::{
104        compound_interest, effective_annual_rate, future_value, net_present_value, present_value,
105        simple_interest,
106    };
107
108    use precision_core::{ArithmeticError, Decimal};
109
110    /// Calculate share price (assets per share).
111    pub fn calculate_share_price(
112        total_assets: Decimal,
113        total_supply: Decimal,
114    ) -> Result<Decimal, ArithmeticError> {
115        if total_supply.is_zero() {
116            return Ok(Decimal::ONE);
117        }
118        total_assets.try_div(total_supply)
119    }
120
121    /// Calculate shares to mint for a deposit (ERC4626).
122    pub fn calculate_shares_for_deposit(
123        assets: Decimal,
124        total_assets: Decimal,
125        total_supply: Decimal,
126    ) -> Result<Decimal, ArithmeticError> {
127        if total_supply.is_zero() {
128            return Ok(assets);
129        }
130        assets.try_mul(total_supply)?.try_div(total_assets)
131    }
132
133    /// Calculate assets to return for redemption (ERC4626).
134    pub fn calculate_assets_for_redeem(
135        shares: Decimal,
136        total_assets: Decimal,
137        total_supply: Decimal,
138    ) -> Result<Decimal, ArithmeticError> {
139        shares.try_mul(total_assets)?.try_div(total_supply)
140    }
141
142    /// Calculate APY from APR given compounding frequency.
143    pub fn calculate_apy_from_apr(
144        apr: Decimal,
145        compounds_per_year: u32,
146    ) -> Result<Decimal, ArithmeticError> {
147        let n = Decimal::from(compounds_per_year as i64);
148        let rate_per_period = apr.try_div(n)?;
149        let base = Decimal::ONE.try_add(rate_per_period)?;
150
151        let mut result = Decimal::ONE;
152        for _ in 0..compounds_per_year {
153            result = result.try_mul(base)?;
154        }
155
156        result.try_sub(Decimal::ONE)
157    }
158
159    /// Calculate performance fee on gains.
160    pub fn calculate_performance_fee(
161        gains: Decimal,
162        fee_bps: Decimal,
163    ) -> Result<Decimal, ArithmeticError> {
164        let bps_base = Decimal::from(10000i64);
165        gains.try_mul(fee_bps)?.try_div(bps_base)
166    }
167}
168
169/// Derivatives and perpetual futures calculations.
170pub mod derivatives {
171    pub use financial_calc::derivatives::{
172        calculate_average_entry_price, calculate_breakeven_price, calculate_effective_leverage,
173        calculate_funding_payment, calculate_funding_rate, calculate_liquidation_distance,
174        calculate_liquidation_price, calculate_margin_ratio, calculate_max_position_size,
175        calculate_pnl, calculate_pnl_percentage, calculate_required_collateral, calculate_roe,
176        FundingParams, PerpPosition,
177    };
178}
179
180/// Options pricing and Greeks.
181pub mod options {
182    pub use financial_calc::options::{
183        black_scholes_call, black_scholes_put, call_greeks, implied_volatility, normal_cdf,
184        normal_pdf, put_greeks, Greeks, OptionParams,
185    };
186}
187
188/// Interest and time value calculations.
189pub mod interest {
190    pub use financial_calc::{
191        compound_interest, effective_annual_rate, future_value, net_present_value, present_value,
192        simple_interest,
193    };
194}
195
196/// Day count conventions for interest calculations.
197pub mod day_count {
198    pub use financial_calc::day_count::{Date, DayCountConvention, YearFraction};
199}
200
201/// Yield curve and term structure.
202pub mod term_structure {
203    pub use financial_calc::term_structure::{
204        CurveNode, FlatTermStructure, PiecewiseTermStructure, TermStructure, MAX_CURVE_NODES,
205    };
206}
207
208/// Numerical solvers.
209pub mod solver {
210    pub use financial_calc::solver::{
211        bisection, brent, default_tolerance, newton_raphson, newton_raphson_numerical, secant,
212        SolverResult, DEFAULT_MAX_ITER,
213    };
214}
215
216/// Interpolation methods.
217pub mod interpolation {
218    pub use financial_calc::interpolation::{
219        CubicSpline, DataPoint, Interpolator, Linear, LogLinear,
220    };
221}
222
223/// Commonly used imports for DeFi calculations.
224pub mod prelude {
225    pub use crate::precision::{ArithmeticError, Decimal, RoundingMode};
226
227    // Lending
228    pub use crate::lending::{
229        collateral_ratio, health_factor, is_healthy, liquidation_price, max_borrowable,
230    };
231
232    // AMM
233    pub use crate::amm::{
234        calculate_impermanent_loss, calculate_liquidity_mint, calculate_price_impact,
235        calculate_spot_price, calculate_swap_output, sqrt_price_to_tick, tick_to_sqrt_price,
236    };
237
238    // Vault
239    pub use crate::vault::{
240        calculate_apy_from_apr, calculate_assets_for_redeem, calculate_performance_fee,
241        calculate_share_price, calculate_shares_for_deposit, compound_interest,
242    };
243
244    // Derivatives
245    pub use crate::derivatives::{
246        calculate_funding_rate, calculate_liquidation_price, calculate_pnl, FundingParams,
247        PerpPosition,
248    };
249
250    // Options
251    pub use crate::options::{black_scholes_call, black_scholes_put, call_greeks, OptionParams};
252
253    // Interest
254    pub use crate::interest::{future_value, net_present_value, present_value, simple_interest};
255}
256
257#[cfg(test)]
258mod tests {
259    use super::prelude::*;
260    use core::str::FromStr;
261
262    fn decimal(s: &str) -> Decimal {
263        Decimal::from_str(s).unwrap()
264    }
265
266    #[test]
267    fn test_lending_health_factor() {
268        let hf = health_factor(
269            decimal("10000"),
270            decimal("5000"),
271            decimal("0.8"),
272        )
273        .unwrap();
274
275        // (10000 * 0.8) / 5000 = 1.6
276        assert_eq!(hf, decimal("1.6"));
277    }
278
279    #[test]
280    fn test_amm_swap() {
281        let output = calculate_swap_output(
282            decimal("1000000"),
283            decimal("1000000"),
284            decimal("1000"),
285            decimal("30"),
286        )
287        .unwrap();
288
289        assert!(output > decimal("996"));
290        assert!(output < decimal("1000"));
291    }
292
293    #[test]
294    fn test_vault_share_price() {
295        let price = crate::vault::calculate_share_price(
296            decimal("1050000"),
297            decimal("1000000"),
298        )
299        .unwrap();
300
301        assert_eq!(price, decimal("1.05"));
302    }
303
304    #[test]
305    fn test_derivatives_pnl() {
306        let position = PerpPosition {
307            size: decimal("1.0"),
308            entry_price: decimal("2000"),
309            is_long: true,
310            leverage: decimal("10"),
311            collateral: decimal("200"),
312        };
313
314        let pnl = calculate_pnl(&position, decimal("2100")).unwrap();
315        assert_eq!(pnl, decimal("100"));
316    }
317
318    #[test]
319    fn test_options_pricing() {
320        let params = OptionParams {
321            spot: decimal("100"),
322            strike: decimal("100"),
323            rate: decimal("0.05"),
324            time: decimal("1.0"),
325            volatility: decimal("0.2"),
326        };
327
328        let call = black_scholes_call(&params).unwrap();
329        assert!(call > decimal("9"));
330        assert!(call < decimal("12"));
331    }
332}