FABRKNT
Build OpenHL Funding — perpetual funding state machine
Orientation
Lesson 1 of 12·CONTENT15 min50 XP

Treat this page as a workbench, not a blog post. The goal is to extract a reusable mental model from the source and carry it into the rest of the Fabrknt stack.

Course
Build OpenHL Funding — perpetual funding state machine
Lesson role
CONTENT
Sequence
1 / 12

Build OpenHL Funding — perpetual funding state machine

Question

Build openhl's funding state machine in Rust. Funding is the convergence force that keeps perp prices anchored to spot (the perp primer detail). This course implements the math + the clock + the conservation laws end-to-end, pinned to openhl SHA cd94137.

Principle (minimum model)

  • 12 lessons across 5 modules. Orientation (L0) → Determinism + types (L1-L3) → Pure compute (L4-L7) → Clock state machine (L8-L10) → Capstone (L11).
  • Pinned to openhl SHA cd94137. Every code example reproduces byte-for-byte. The Capstone answer-key is at this SHA.
  • Three discipline strands. Determinism (consensus-safe arithmetic) + types (newtypes for prices / premiums / notional) + invariants (zero-sum funding, no-catch-up, interval-gating).
  • Prerequisites. openhl Liquidation L9 (WithdrawOutcome proptest semantics) + perp primer (funding mechanics).
  • Pure compute everywhere. No I/O, no async, no state mutation inside the math. Reusable in proptest + unit tests, easy to formally analyse.
  • Saturating arithmetic. saturating_add / saturating_sub everywhere. Wrapping is consensus-incompatible; saturating is the canonical Hyperliquid choice.
  • Discrete event clock. Funding only fires at interval boundaries (no catch-up across missed intervals). The clock state machine enforces this in pure Rust.

Worked example + steps

Build OpenHL Funding — perpetual funding state machine

What you'll build

The previous course (building-openhl-precompiles) plugged custom EVM precompiles into Reth so smart contracts can read and write a live CLOB. This course builds the next openhl primitive: the funding state machine that drives perpetual-contract funding payments.

By the end of this course, you'll have shipped:

  • 3 source files / ~635 lines of code (LOC) in a new openhl-funding crate.
  • 22 tests passing: 20 hand-traced + 2 proptest (premium antisymmetry + balanced-book zero-sum).
  • 3 building blocks: a fixed-point types module, a pure compute module (premium / rate / settlement), and a tick-gating clock state machine.
  • Two enforced invariants on the clock: at most one settlement per interval; no catch-up after long gaps.

You'll understand:

  • Why floating-point arithmetic is a network-fork hazard in consensus systems.
  • The Hyperliquid funding-rate shape: premium → rate → settlement, with divisor + cap.
  • How fixed-point integers scaled by RATE_SCALE = 1_000_000_000 (parts-per-billion) get you 9 decimal digits of precision without consensus risk.
  • Why pure state machines + saturating arithmetic (operations that clamp to min/max on overflow instead of panicking — saturating_add / saturating_mul in Rust) are the right shape for consensus-critical math.
  • Why the clock advances to now (not to last_settled + interval) — and the design trade-off that encodes.

Why funding matters (1-paragraph perp recap)

Perpetual futures don't expire. So how does the mark price stay anchored to the spot/index price? Funding payments. When mark > index (longs are overpaying relative to spot), longs pay shorts on a fixed cadence — typically every interval (HL: 1 hour). When mark < index, shorts pay longs.

Funding rate is assembled in stages:

1. Premium    = (mark - index) / index               ← still a raw, dimensionless ratio at this point
2. Rate       = Premium / divisor                    ← divisor = 8 (HL)
3. Capped     = clamp(Rate, -4%/interval, +4%/interval)   ← network-set absolute cap
4. Settlement = size × mark × Capped                 ← quote-currency amount each non-zero position settles per tick

The (mark - index) / index premium introduced here is a raw ratio, and this course never implements it as f64. Module 1 (Lesson 1) bridges it to a signed integer scaled by RATE_SCALE = 1_000_000_000 (parts-per-billion), and every Premium / Rate / Capped / Settlement computation downstream lives entirely in that fixed-point representation, deterministically. Longs pay, shorts receive — or vice versa, depending on the sign of the premium.

Why funding can't use floats

A consensus L1 validator must compute exactly the same funding rate as every other validator. If two validators disagree on the last bit — the least-significant bit (LSB) — of a rate, they fork the chain.

Float arithmetic gives different bit patterns across:

  • Compilers — LLVM may emit FMA (fused multiply-add) on one CPU and split it on another.
  • CPUs — different rounding modes, different denormal handling.
  • Operations(a * b) + c and a * b + c can compile to identical-looking IR but produce different LSBs after optimization.

The cost of a one-LSB disagreement on a funding rate is chain divergence. Validators on different sides of the fork settle different deltas, balances diverge, the next block won't validate against either chain.

The fix: never use floats. Compute everything in signed integers scaled by RATE_SCALE = 1_000_000_000 (parts-per-billion). 0.04 (4%) is 40_000_000. 0.001 (0.1%) is 1_000_000. Multiplication needs i128 intermediate to avoid overflow; division comes after.

This is the same constraint Solana's compute budget, Ethereum's EVM, and every other consensus system imposes. Determinism is the whole game.

The 12 lessons

Module 0 — Orientation

  • Lesson 0 (this lesson) — Why funding, why fixed-point, why state machine.

Module 1 — Determinism + types (Lessons 1–3)

  • Lesson 1RATE_SCALE = 1e9: fixed-point scheme, why integers, what 9 decimal digits buys you.
  • Lesson 2 — Money types: MarkPrice, IndexPrice, Premium, Notional. Why each is its own newtype, not just i64.
  • Lesson 3 — Position types: PositionSize, Position, Settlement, FundingParams. The HL defaults and what each parameter encodes.

Module 2 — Pure compute (Lessons 4–7)

  • Lesson 4compute_premium: the (mark - index) / index derivation. Tests for sign symmetry.
  • Lesson 5saturate_i128_to_i64 + overflow philosophy. Why saturate, why not panic.
  • Lesson 6compute_rate: divisor, cap, HL-style defaults. The clamp behavior.
  • Lesson 7apply_funding: longs-pay-shorts sign convention. Balanced-book zero-sum invariant.

Module 3 — Clock state machine (Lessons 8–10)

  • Lesson 8FundingClock structure + tick() interface.
  • Lesson 9 — Interval-gating invariant: at most one settlement per interval. Tests at the boundary.
  • Lesson 10 — No-catch-up invariant: 10-interval gap settles ONCE, not ten times. Why.

Module 4 — Capstone (Lesson 11)

  • Lesson 11 — Synthesis. Bridge integration preview (where funding plugs into LiveRethEvmBridge). Honest deferred: oracle, liquidations, basis-vs-fixed funding.

SHA pinning per module

Every lesson cites the openhl commit it builds against. For this course, all 12 lessons cite Stage 8b cd94137 — funding is a single self-contained commit. (Compare to Course 8, which spanned 5 commits across Stage 9a-9d.) The clean SHA mapping means the answer-key diff at the end of Lesson 11 is crates/funding/ byte-identical against cd94137.

ModuleLessonsSHA
0Lesson 0cd94137
1Lessons 1–3cd94137
2Lessons 4–7cd94137
3Lessons 8–10cd94137
4Lesson 11cd94137

Prerequisites

To get the most from this course you should have:

  • Course 6 (openhl-consensus) and Course 7 (openhl-clob) in your head as conceptual background — the funding state machine consumes AccountId (Course 7) and is targeted at the bridge built in Courses 6 and 7. You can skip Course 8 (precompiles) and still follow this course — funding is pure state-machine math, not EVM-side wiring.
  • Comfort with i128 arithmetic in Rust — at least one prior as i128 upcast for overflow avoidance in your bag.
  • A passing familiarity with perpetual-futures funding mechanics. If you've never traded a perp, the 1-paragraph recap above is enough. If you've traded a perp on Hyperliquid, you're set.
  • No EVM-specific knowledge. This course doesn't touch precompiles, contracts, or RPC.

You do NOT need:

  • A running openhl node (the funding crate has zero I/O).
  • Solana or any other L1 experience.
  • Quantitative finance background — the math here is straightforward fixed-point arithmetic.

Setup

# In your openhl workspace root:
cd ~/code/my-openhl
git checkout main
cargo build --workspace  # baseline — should pass before Lesson 1

Reference checkout (for the answer-key diff at the end of each lesson):

cd ~/code/openhl-reference  # separate checkout from your work tree
git checkout cd94137

(Or use the same workspace and git stash between lookups. Either works.)

Course style

Each lesson follows the build-along format established in Courses 6-8:

  • Goal — what passes / what's built by the end.
  • Recap — where the previous lesson left off.
  • Plan — the specific edits, numbered.
  • Predict callouts (🛑 with "Before scrolling...") — questions before answers, so the answers stick.
  • Anti-fluency callouts (🛑 with common misconceptions named explicitly) — preempt the "couldn't we just...?" reflex.
  • Walk-through — step-by-step code edits with explanation per change.
  • Test — the cargo test command to run + expected output.
  • Design reflection — 3-5 load-bearing decisions encoded in this lesson's code.
  • Answer keygit diff against the openhl reference SHA.
  • Common questions — 3-5 questions with grounded answers.

The math content (especially Modules 2-3) is more concept-heavy than code-heavy compared to Course 8. Plan to slow down at the formulas — they're short, but they need to compute the right thing for every input you can imagine. A perp funding bug doesn't crash; it silently shifts wealth.

Ready

Onward to Lesson 1, where we set up the RATE_SCALE constant and the fixed-point scheme that everything else builds on.

Summary (3 lines)

  • Build openhl funding state machine = funding math + clock + invariants. 12 lessons / 5 modules. Pinned to SHA cd94137.
  • Three discipline strands: determinism + types + invariants. Pure compute throughout; saturating arithmetic for consensus safety.
  • Prerequisites: openhl Liquidation L9 + perp primer. Discrete event clock enforces no-catch-up.