A Rust library implementing core DeFi primitives from scratch as a learning exercise. Built with a focus on correctness, integer arithmetic, and type safety.
| Module | Description |
|---|---|
amm::constant_product |
Uniswap V2-style x*y=k AMM |
amm::concentrated_liquidity |
Uniswap V3-style concentrated liquidity with tick crossing |
orderbook::engine |
Price-time priority limit order book with market order support |
- Integer arithmetic — no floating point in the constant product AMM.
All math uses
checked_*operations with explicit overflow handling. - Type-safe token pairs —
Pool<SOL, USDC>andPool<USDC, SOL>are distinct types at compile time via theTokenMarkertrait andPair<Base, Quote>phantom type. You cannot accidentally swap token roles. - Explicit errors — every fallible operation returns
DefiResult<T>, a type alias forResult<T, DefiError>. All error variants are typed and carry context (amounts, ticks, prices). - Zero unsafe code.
[dependencies]
defi-primitive = "0.1"use defi_primitive::{
amm::constant_product::ConstantProductPool,
orderbook::{engine::OrderBook, order::{Order, OrderSide}},
token::TokenMarker,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)] struct SOL;
#[derive(Debug, Clone, PartialEq, Eq, Hash)] struct USDC;
impl TokenMarker for SOL {}
impl TokenMarker for USDC {}
// --- Constant product AMM (0.3% fee) ---
let mut pool = ConstantProductPool::<SOL, USDC>::new(30)?;
pool.add_liquidity(1_000_000, 100_000_000)?;
let quoted = pool.quote_x_for_y(10_000)?;
let out = pool.swap_x_for_y(10_000, quoted)?; // slippage = exact quote
let shares = pool.lp_supply;
let (sol_back, usdc_back) = pool.remove_liquidity(shares / 2)?;
// --- Limit order book ---
let mut book = OrderBook::<SOL, USDC>::new();
book.place_order(Order::new_limit(1, OrderSide::Ask, 100_000, 50, 0))?;
let trades = book.place_order(Order::new_limit(2, OrderSide::Bid, 100_000, 30, 1))?;
// trades[0].price == 100_000 (execution at maker's price)
// trades[0].quantity == 30Implements the x * y = k invariant with configurable swap fees.
output = (reserve_out × amount_in × (10_000 - fee_bps))
/ (reserve_in × 10_000 + amount_in × (10_000 - fee_bps))
Fees are captured in reserves, so k only ever increases after swaps.
LP shares are minted as sqrt(x * y) on first deposit and proportionally
on subsequent deposits. All arithmetic uses u128 with checked_* throughout.
Implements the Uniswap V3 tick architecture:
- Liquidity is deposited into
[tick_lower, tick_upper)ranges - A sparse
BTreeMap<Tick, TickInfo>tracks all initialized ticks - Each tick stores
liquidity_net: i128(net change on crossing) andliquidity_gross: u128(reference count for cleanup) - The swap engine iterates initialized ticks in the direction of the trade,
crossing each boundary and updating active liquidity via
liquidity_net
Tick crossing convention (matches Uniswap V3):
add_position(tick_lower, tick_upper, L):
tick_lower.liquidity_net += L // entering range upward → activate L
tick_upper.liquidity_net -= L // leaving range upward → deactivate L
cross tick upward: active_liquidity += tick.liquidity_net
cross tick downward: active_liquidity -= tick.liquidity_net
Precision note: uses
f64for tick-to-sqrt-price conversion. Production implementations (Uniswap V3, Orca Whirlpools) use Q64.96 fixed-point arithmetic for deterministic on-chain behavior.
Price-time priority matching engine:
- Bids sorted descending (
BTreeMapwithnext_back()), asks ascending HashMap<order_id, (side, price)>index for O(1) cancellation- Execution always at the maker's price (resting order)
- Supports limit and market orders; partial fills leave remainder in book
- Market orders do not rest — unfilled quantity is dropped
match pool.swap_x_for_y(amount, min_out) {
Err(DefiError::SlippageExceeded { got, minimum }) => { /* retry */ }
Err(DefiError::ZeroLiquidity) => { /* pool empty */ }
Err(DefiError::MathOverflow) => { /* amounts too large */ }
Ok(out) => { /* success */ }
}# All tests
cargo test
# Unit tests only (inside src/)
cargo test --lib
# Property-based tests only
cargo test --test property_tests
# More proptest iterations
PROPTEST_CASES=1000 cargo test --test property_testssrc/
├── lib.rs # crate root, public re-exports
├── error.rs # DefiError enum, DefiResult<T>
├── token.rs # TokenMarker trait, Pair<Base, Quote>
├── amm/
│ ├── mod.rs
│ ├── constant_product.rs # ConstantProductPool<X, Y>
│ └── concentrated_liquidity.rs # ConcentratedLiquidityPool<X, Y>
└── orderbook/
├── mod.rs
├── engine.rs # OrderBook<Base, Quote>
└── order.rs # Order, OrderSide, OrderType, Trade
tests/
└── property_tests.rs # proptest invariant tests