Skip to content

ChiamakaUI/defi-primitive

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

defi-primitive

A Rust library implementing core DeFi primitives from scratch as a learning exercise. Built with a focus on correctness, integer arithmetic, and type safety.

What's included

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

Design principles

  • Integer arithmetic — no floating point in the constant product AMM. All math uses checked_* operations with explicit overflow handling.
  • Type-safe token pairsPool<SOL, USDC> and Pool<USDC, SOL> are distinct types at compile time via the TokenMarker trait and Pair<Base, Quote> phantom type. You cannot accidentally swap token roles.
  • Explicit errors — every fallible operation returns DefiResult<T>, a type alias for Result<T, DefiError>. All error variants are typed and carry context (amounts, ticks, prices).
  • Zero unsafe code.

Quick start

[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 == 30

Constant product AMM

Implements 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.

Concentrated liquidity

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) and liquidity_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 f64 for tick-to-sqrt-price conversion. Production implementations (Uniswap V3, Orca Whirlpools) use Q64.96 fixed-point arithmetic for deterministic on-chain behavior.

Order book

Price-time priority matching engine:

  • Bids sorted descending (BTreeMap with next_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

Error handling

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 */ }
}

Running tests

# 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_tests

Project structure

src/
├── 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

About

Uniswap V2/V3-style DeFi primitives in Rust — constant product AMM, concentrated liquidity with tick crossing, and a price-time priority order book. No unsafe code.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages