All intermediate arithmetic must use BN (arbitrary-precision integers). Floating-point is permitted only for final display formatting (e.g., formatUsd, formatLeverage). Intermediate computations must never convert to number or use JavaScript arithmetic operators on financial values.
No implicit scaling. Every multiplication or division that involves a precision constant must explicitly reference the constant by name:
// Correct
const leverage = sizeUsd.mul(BPS_POWER).div(marginUsd);
// Incorrect
const leverage = sizeUsd.mul(new BN(10000)).div(marginUsd);Every function that performs a computation must document:
- Input units -- What precision each parameter uses (e.g., USD_DECIMALS, ORACLE_PRICE_SCALE, BPS_DECIMALS)
- Output units -- What precision the return value uses
- Rust source -- File path and function name in the reference implementation
- Assumptions -- What must be true for the computation to be valid
- Production divergence -- Known or suspected differences from the closed-source production program
Example:
/**
* Compute current leverage in BPS (10^4 precision).
*
* Rust source: flash-perpetuals/programs/perpetuals/src/state/pool.rs
* -> fn get_leverage (lines ~438-468)
*
* Formula:
* current_leverage = size_usd * BPS_POWER / current_margin_usd
*
* Precision:
* Input: size_usd and margin_usd in USD_DECIMALS (10^6)
* Output: leverage in BPS_DECIMALS (10^4). E.g., 10x = 100,000 BPS.
*
* Assumptions:
* - margin_usd >= 0 (pre-clamped by computeMargin).
*
* PRODUCTION DIVERGENCE: None expected.
*/All mathematical formulas must reference their Rust source. If a formula is derived (not directly implemented in Rust), document the derivation steps explicitly in the JSDoc header. Use the format:
Rust source: <file_path> -> fn <function_name>
If the formula is derived rather than direct:
Rust source: Derived from the liquidation condition in <file_path> -> fn <function_name>
Runtime guards (src/guards.ts) must:
- Be pure functions (no side effects beyond throwing)
- Include the
contextparameter in error messages for debugging - Document what invariant they protect
- Never fire under normal operation -- they catch impossible states
Commits should be logically grouped by subsystem:
risk-engine:-- Core computation changesoracle:-- Oracle/pricing logicmetrics:-- Aggregation and display metricscli:-- CLI, audit mode, presentationguards:-- Runtime validationdocs:-- Documentation onlyinfra:-- Build, config, dependencies
- Use the interfaces defined in
src/types.tsfor all on-chain account data - Do not use
anyexcept at the Anchor deserialization boundary indecoder.ts - All BN values must be typed as
BN, notnumber
src/constants.ts-- These are verified against multiple sources. Changes require re-verification.src/types.ts-- These match the IDL account layouts. Changes require IDL version update.scripts/setup.sh-- Downloads the canonical IDL. Changes require verification of the new source URL.
- Fork the repository
- Create a feature branch from
main - Ensure
npm run lintpasses with no errors - Ensure all existing sanity checks pass when running
npm start - If adding new computations, include the Rust source reference and unit documentation
- Submit a pull request with a description of what changed and why