FABRKNT
Step 4. Funding:決定論的数学パイプラインと Funding ステートマシンの構築
Determinism と型
レッスン 2 / 12·CONTENT25 分50 XP
コース
Step 4. Funding:決定論的数学パイプラインと Funding ステートマシンの構築
レッスンの役割
CONTENT
順序
2 / 12

レッスン1 — RATE_SCALE — consensus を守る定数

問い

funding の全計算(premium / rate / settlement)を float なしで、全 validator が bit-exact に一致させたい。何を基準単位に選び、それを i64 のどこに置けば精度とヘッドルームを両立できるか?

原理(最小モデル)

  • float は consensus で動かない。 FMA・丸めモード・denormal の扱いはコンパイラ/CPU ごとに違うビットを生み、rate の 1 LSB ズレが chain fork に直結する。
  • RATE_SCALE = 1e9(parts-per-billion)が i64 のスイートスポット。 9 桁精度 + 積の中間値でも i64 まで 11 桁ヘッドルーム。1e6 は精度不足(10 ppb が 0 に丸まる)、1e12 はヘッドルーム不足(積に i256 が要る)。
  • i64u64 でない。 rate/premium は符号付き(longs 支払い=正、shorts 支払い=負)。符号付きなら compute の符号チェックが不要、i128 中間値が積を吸収する。
  • RATE_SCALE は consensus 定数であって調整パラメータでない。 デプロイ後は immutable(全 balance / 全 settlement / 全 fixture がこれ前提に calibrate)。crate 開始時に一度 const で設定する。

具体例

ヘッドルームを桁の物差しに並べる:

桁                                     値                                     何が住んでいるか
─────────────────────────────────────────────────────────────────────────────
1e18  ────────  9_223_372_036_854_775_807  ───────  i64::MAX (約 920 京)
1e18                                                ↑ 中間値の天井
1e15  ────────  1_600_000_000_000_000     ───────  4% × 4% (キャップ²) = 1.6e15
1e9   ────────  1_000_000_000             ───────  RATE_SCALE = 100% (10 億)
1e7   ────────       40_000_000           ───────  HL Funding Cap 4% (4 千万)
1e6   ────────        1_000_000           ───────  0.1%
1e1   ────────               10           ───────  0.0001% = 10 ppb (現実的な最小粒度)

キャップ 4e7RATE_SCALE 1e9 の間に 1 桁以上、キャップ²でも 1.6e15 で i64 天井 9.2e18 まで 3 桁 — 「rate × notional」程度の積は i128 で吸収し、最後に RATE_SCALE で割って i64 に戻せる。最小粒度 10 ppb(0.0001%)も表現できる(1e6 だと 0 に丸まる)。

失敗例(誤解)

f64 で計算して validator 間共有の前に N 桁に丸めればいい」は誤り、2 つの理由: (1) 中間計算が最終丸めより先に発散し、その時点で被害が出ている (2)「N 桁に丸める」自体が float 演算で処理系ごとに挙動が違う。float 非決定性からの脱出口は整数より単純なものはない。


ここまでで「なぜ 1e9 の整数か」は着地した。ここから crate を「public な値を 1 つ持つ実 crate」に変える(編集 3 つ、テストなし — RATE_SCALE は値であって挙動でない。最初のテストはレッスン2)。コードは完全形。

🛑 予測。 RATE_SCALE1e6(ppm)や 1e12(ppt)でなく 1e9 にする理由は?(答え: i64 max ~9.2e18。1e9 なら 9 桁精度 + キャップ 4e7 から天井まで 11 桁ヘッドルーム。1e12 は精度↑だが積に i256 が要る。1e6 は 10 ppb が 0 に丸まり精度不足。1e9 が i64 固定小数点 rate のスイートスポット。)

ステップで組み立てる

Step 1: Cargo.toml を更新

crates/funding/Cargo.toml[dependencies] 以下をこうする:

[dependencies]
openhl-clob = { path = "../clob" }

[dev-dependencies]
proptest = { workspace = true }

[lints]
workspace = true

openhl-clobAccountId がレッスン3 の Position で要る — production の型シグネチャの一部なので dev-dep でなく通常 dep)+ [dev-dependencies] proptest(レッスン4/7 で使う)。今宣言して Cargo.toml の diff を 1 箇所に集中させる(path dep は最初の use まで recompile を走らせないのでコストほぼゼロ)。

Step 2: src/types.rs を作成

新規ファイル。module doc と RATE_SCALE のみ:

//! Core types for the funding state machine.
//!
//! Pure data — no I/O, no allocation beyond what's needed for settlements.
//! Every type is `Copy`-friendly (or, in the case of `Position`, `Clone +
//! Copy`) so callers can pass snapshots without lifetime gymnastics.
//!
//! ### Why fixed-point integers, not floats
//!
//! Consensus determinism — every validator must compute the *same* funding
//! rate from the *same* inputs. Float arithmetic gives different bit patterns
//! across compilers and CPUs (FMA, rounding mode, denormal handling); the
//! moment two validators disagree on a single LSB they fork. We use signed
//! integers scaled by [`RATE_SCALE`] (parts-per-billion) for rates and
//! premiums, and a separate `Notional` type for quote-currency deltas.

/// Scale factor for [`FundingRate`] and [`Premium`]. A raw value of
/// `RATE_SCALE` represents `1.0` (i.e., 100%). With `1e9` we get 9 decimal
/// digits of precision — more than enough for funding rates that typically
/// sit in the ±0.01% to ±0.05% per interval band.
pub const RATE_SCALE: i64 = 1_000_000_000;

module doc に「Why fixed-point integers, not floats」を置く — crate 全体の load-bearing な理由付けで、コミットメッセージに埋もれさせず最上部に置く。[FundingRate]/[Premium] クロス参照はまだリンク切れ(レッスン2/3 で解決、warning は受け入れる)。i64u64 でない)— rate/premium は符号付き。

Step 3: src/lib.rs を更新

空だった lib.rs をこう置き換える:

//! `openhl-funding` — funding-rate state machine.
//!
//! Pure state machine: no I/O, no async, no networking. Funding is applied
//! deterministically on a fixed cadence (see [`FundingClock`]); every tick is
//! a pure function over `(now, mark, index, positions)` → settlements.
//!
//! ### Hyperliquid-shape funding, in one paragraph
//!
//! Perpetual contracts don't expire, so the mark price can drift arbitrarily
//! from the spot ("index") price. Funding payments push it back: when mark >
//! index (longs are overpaying), longs pay shorts; when mark < index, shorts
//! pay longs. The premium `(mark - index) / index` is divided by a
//! per-day-interval count (HL: divisor 8 — one settlement every 1 hour, scaled to a daily rate) to derive a
//! per-interval rate, capped at a network-set absolute max. At each tick
//! every account with an open position settles `position_size * mark * rate`
//! in quote currency.
//!
//! Integration with the rest of openhl happens at the EVM bridge: settlement
//! deltas become balance updates that the bridge bundles into payloads. That
//! integration lives in `crates/evm/`; the rate math and tick gating are here.

pub mod types;

pub use types::RATE_SCALE;

pub mod 宣言は対応ファイルを作るタイミングで足す(pub mod compute; を書いて compute.rs がないと error[E0583])。pub use types::RATE_SCALE で呼び出し側は openhl_funding::RATE_SCALE の短いパスを使える。

Step 4: コンパイル

cargo build -p openhl-funding
warning: unresolved link to `FundingRate`
warning: unresolved link to `Premium`
warning: unresolved link to `FundingClock`
    Finished `dev` profile [unoptimized + debuginfo] in 0.5s

rustdoc warning 3 つは期待通り(リンク先はレッスン2/3/8 で順次追加)。#[allow(rustdoc::broken_intra_doc_links)] で抑制しない — 「まだ X を足す必要がある」インジケータとして有用。

答え合わせ

cd ~/code/openhl-reference && git checkout cd94137
diff -u ~/code/my-openhl/crates/funding/Cargo.toml ./crates/funding/Cargo.toml
diff -u ~/code/my-openhl/crates/funding/src/types.rs ./crates/funding/src/types.rs
diff -u ~/code/my-openhl/crates/funding/src/lib.rs ./crates/funding/src/lib.rs
git checkout main

Cargo.toml は cd94137 と完全一致、types.rs は最初の ~30 行(module doc + RATE_SCALE)まで一致、lib.rs は短い版(pub mod types; + re-export 1 つ)。

合格基準

cargo build -p openhl-funding が通り、rustdoc warning 3 つ(unresolved link、期待通り、抑制しない)。よくあるエラー: E0463openhl-clob 行を忘れ)/ E0583pub mod clock/compute を先取り)/ manifest parse([dev-dependencies] の typo)。

まとめ(3行)

  • RATE_SCALE = 1_000_000_000(parts-per-billion)— funding の全計算が乗る基準単位。float は 1 LSB のズレで chain fork を招くので使わず、すべて符号付き整数で bit-exact に計算する。
  • 1e9 は i64 のスイートスポット: 9 桁精度 + キャップ 4e7 から天井まで 11 桁ヘッドルーム(i128 中間値で積を吸収して最後に割り戻せる)。
  • consensus 定数なのでデプロイ後は immutable。i64(符号付き)で crate 開始時に一度 const 設定。テストなし(値であって挙動でない)。

次のレッスン(レッスン2)

money type を 4 つ追加する(MarkPrice/IndexPrice/Premium/Notional)。焦点は「なぜ固定小数点」から「なぜ newtype」へ — MarkPrice を期待する箇所に IndexPrice を渡す引数順バグをコンパイル時に潰す。