UNPKG

granite-math-sdk

Version:

Granite math sdk

221 lines (194 loc) 6.28 kB
import { absoluteMaxLeverage, correctedMaxLTV, unencumberedCollateral, leverageMaxSlippage, swapLoss, computeFlashLoanValues, convertDebtSharesToAssets, InterestRateParams, } from "../../src"; import { createCollateral } from "../utils"; describe("Leverage module tests", () => { const defaultIrParams: InterestRateParams = { urKink: 0.7, baseIR: 0.5, slope1: 0.75, slope2: 1.5, }; describe("absoluteMaxLeverage", () => { it("computes 1 over 1 minus maxLTV", () => { expect(absoluteMaxLeverage(0.7)).toBe(1 / (1 - 0.7)); }); it("throws when maxLTV is invalid", () => { expect(() => absoluteMaxLeverage(1)).toThrow("Invalid maxLTV"); expect(() => absoluteMaxLeverage(0)).toThrow("Invalid maxLTV"); }); }); describe("correctedMaxLTV", () => { it("returns zero when fees consume the entire LTV", () => { const collateral = createCollateral(100, 10, 0.7, 0.7); const result = correctedMaxLTV(collateral, 1, 0); expect(result).toBe(0); }); it("applies fees and slippage to maxLTV", () => { const collateral = createCollateral(100, 1, 0.8, 0.8); const result = correctedMaxLTV(collateral, 0.01, 0.02); expect(result).toBeCloseTo((1 - 0.01 - 0.02) * 0.8); }); it("throws when maxLTV is missing", () => { const badCollateral = { amount: 100, price: 1 } as any; expect(() => correctedMaxLTV(badCollateral, 0.01, 0.01)).toThrow( "Invalid maxLTV", ); }); }); describe("unencumberedCollateral", () => { it("equals total collateral value when debt shares are zero", () => { const collateral = createCollateral(100, 2, 0.5, 0.5); const result = unencumberedCollateral( 0, // debtShares 10_000, // openInterest 10_000, // totalDebtShares 20_000, // totalAssets defaultIrParams, 3600, // timeDelta collateral, 0.7, // maxLTVcorrected ); expect(result).toBeCloseTo(100 * 2); }); it("becomes negative when debt is above the max LTV limit", () => { const collateral = createCollateral(200, 1, 0.6, 0.6); const result = unencumberedCollateral( 1_000, 20_000, 10_000, 40_000, defaultIrParams, 7200, collateral, 0.6, // maxLTVcorrected ); expect(result).toBeLessThan(0); }); it("is zero at the boundary when debt equals collateral value times maxLTV", () => { // Remove time accrual so the share to asset ratio is exact // openInterest divided by totalDebtShares equals 2 assets per share // Need 120 assets of debt to hit the boundary which equals 60 shares const collateral = createCollateral(200, 1, 0.6, 0.6); const result = unencumberedCollateral( 60, 20_000, 10_000, 40_000, defaultIrParams, 0, // no accrual collateral, 0.6, // maxLTVcorrected ); expect(result).toBeCloseTo(0, 10); }); it("matches manual computation using convertDebtSharesToAssets", () => { const collateral = createCollateral(100, 2, 0.5, 0.5); // total value 200 const debtShares = 1_000; const openInterest = 10_000; const totalDebtShares = 10_000; const totalAssets = 20_000; const timeDelta = 3600; const debtAssets = convertDebtSharesToAssets( debtShares, openInterest, totalDebtShares, totalAssets, defaultIrParams, timeDelta, ); const expected = 200 - debtAssets / (collateral.maxLTV as number); const result = unencumberedCollateral( debtShares, openInterest, totalDebtShares, totalAssets, defaultIrParams, timeDelta, collateral, collateral.maxLTV as number, // pass through unchanged when comparing to old formula ); expect(result).toBeCloseTo(expected); }); it("is lower with higher utilization or longer time", () => { const collateral = createCollateral(150, 1, 0.6, 0.6); const base = unencumberedCollateral( 1_000, 10_000, // lower utilization 10_000, 100_000, // assets large defaultIrParams, 3600, collateral, 0.6, ); const higherUtil = unencumberedCollateral( 1_000, 80_000, // higher utilization 10_000, 100_000, defaultIrParams, 3600, collateral, 0.6, ); const longerTime = unencumberedCollateral( 1_000, 10_000, 10_000, 100_000, defaultIrParams, 21_600, // longer time collateral, 0.6, ); expect(higherUtil).toBeLessThan(base); expect(longerTime).toBeLessThan(base); }); }); }); describe("leverageMaxSlippage", () => { it("grows with target leverage and slippage", () => { const low = leverageMaxSlippage(100, 0.2); const highLev = leverageMaxSlippage(100, 0.4); const highSlip = leverageMaxSlippage(100, 0.2); expect(highLev).toBeGreaterThan(low); expect(highSlip).toBe(low); }); it("throws when slippage is invalid", () => { expect(() => leverageMaxSlippage(100, 1)).toThrow("Invalid slippage"); expect(() => leverageMaxSlippage(100, -0.1)).toThrow("Invalid slippage"); }); }); describe("swapLoss", () => { it("computes flash loan minus received value", () => { const result = swapLoss(1000, 9, 100, 90); // received value equals 9 * 100 / 90 equals 10 // swap loss equals 1000 minus 10 equals 990 expect(result).toBeCloseTo(990); }); }); describe("computeFlashLoanValues", () => { it("derives flash loan notional and implied slippage", () => { const newCollateralValue = 1000; const loss = 25; const { flashLoanValue, slippage } = computeFlashLoanValues( newCollateralValue, loss, ); expect(flashLoanValue).toBeCloseTo(1025); expect(slippage).toBeCloseTo(25 / 1025); }); it("handles zero loss", () => { const { flashLoanValue, slippage } = computeFlashLoanValues(500, 0); expect(flashLoanValue).toBe(500); expect(slippage).toBe(0); }); });