レッスン0 — OpenHL Funding を作る(永久先物 funding state machine)
問い
前コース(Precompiles)は、コントラクトが同じ Reth node 上の CLOB を read/write できる地点で終わった。だが perp はまだ perp になっていない — mark price を index(spot)に anchor する仕組みがない。永久先物には期限がないのに、価格はどうやって spot に引き寄せられるのか?
注: OpenHL コースのコードブロックは原則として手元で実行可能な形で示す。ただし
<file>などのプレースホルダや答え合わせ用コマンドは、各レッスンの指示に従って置換してから実行すること。
原理(最小モデル)
- funding 支払いが mark を index に anchor する。 Mark > index(longs が spot 比で overpay)なら longs→shorts、Mark < index なら shorts→longs を、固定 interval(HL は 1 時間)ごとに支払う。これが期限なしでも価格を spot に縛る力。
- パイプラインは 4 段: premium → rate → capped → settlement。
(mark-index)/index(生の比率)→/divisor(HL=8)→clamp(±4%/interval)(ネットワーク上限)→size × mark × capped(各 tick の決済額)。 - float は一切使わない — consensus が壊れる。 全 validator が 完全に同じ rate を計算しなければ、1 LSB のズレでチェーンが fork する。固定小数点整数
RATE_SCALE = 1_000_000_000(parts-per-billion)で、9 桁精度を決定的に得る。 - 純粋な state machine + 飽和演算。 I/O ゼロ・外部依存ゼロ。overflow は panic でなく saturate(最大/最小に張り付く)— consensus 中核の数学に正しい形。
具体例
1. Premium = (mark - index) / index ← 生の比率(無次元)
2. Rate = Premium / divisor ← divisor = 8 (HL)
3. Capped = clamp(Rate, -4%/interval, +4%/interval) ← 絶対上限
4. Settlement = size × mark × Capped ← 各 tick の決済 quote 額
すべて RATE_SCALE でスケールした符号付き整数の上で計算する(0.04=40_000_000、0.001=1_000_000)。乗算は overflow 回避に i128 中間値、除算はその後。Premium の符号が「longs が払うか shorts が受け取るか」を決める。
失敗例(誤解)
「funding rate は f64 で計算して最後に整数化すればいい」は誤り — float は compiler / CPU / 演算順 で異なるビットパターンを生む(LLVM の FMA 分解、丸めモード差、(a*b)+c の最適化差)。1 LSB のズレのコストは チェーン fork だ:fork の別側の validator が異なる delta を決済し、balance が乖離し、次ブロックがどちらのチェーンに対しても検証されない。consensus システムでは float を一切使わず、すべて RATE_SCALE でスケールした符号付き整数で計算する。Solana の compute budget も Ethereum の EVM も同じ制約 — Determinism がすべてを決める。
ここまでで「funding が mark を anchor する」「float は consensus を壊す」は着地した。ここから先はスコープ・前提・12 レッスンのロードマップに入る。L1 以降は実際に Rust を書く。
🛑 予測。 2 つの validator が同じ funding rate を計算する。片方が
f64の(mark-index)/indexを、もう片方が別 CPU で同じ式を計算したら、結果は bit 単位で一致するか?(答え: 保証されない。丸めモード・FMA 分解・演算順で最下位ビットが食い違いうる。funding rate の 1 LSB 不一致 = チェーン fork。だから固定小数点整数で計算する — 整数演算は全プラットフォームで bit-exact。)
終了時に手にするもの
新規 crates/funding/ crate、3 ソースファイル / 約 635 行:
- 固定小数点 types モジュール —
RATE_SCALE定数、MarkPrice/IndexPrice/Premium/Notionalの金額 newtype、Position/Settlement/FundingParams。 - 純粋な compute モジュール —
compute_premium/saturate_i128_to_i64/compute_rate/apply_funding。 - clock state machine —
FundingClock+tick()、interval gating を担う。 - 22 テスト:手書き 20 + proptest 2(premium 反対称性、balanced-book zero-sum)。clock 不変条件 2 つ(interval ごと settlement は最多 1 回、長時間ギャップ後の catch-up なし)を強制。
終了時にも手にしないもの(意図的な scope cut)
- Oracle(index price の供給元) — funding は index を 入力 として受け取るだけ。
- Liquidation(= Step 5)。
- Basis-vs-fixed funding — HL スタイルの fixed-interval funding のみ。
- Bridge 統合 — funding state machine は純粋な数学として完結。
LiveRethEvmBridgeへの plug-in は capstone でプレビューし、実装は下流。
前提
- Step 1(Consensus)/ Step 2(CLOB) をコンセプト背景として(funding は
AccountIdを受け取り、bridge に接続される)。Step 3(Precompiles)はスキップ可 — funding は純粋な state-machine 数学で、EVM 接続作業ではない。 - Rust の
i128演算に慣れていること(overflow 回避のas i128upcast を 1 回以上経験していれば十分)。 - 永久先物 funding メカニクスに最低限の馴染み(上の 1 段落で足りる)。
- EVM 知識は不要(precompile / コントラクト / RPC に触れない)。
不要: 動作中の openhl ノード(I/O ゼロ)/ 他 L1 チェーン経験 / 定量金融バックグラウンド。
セットアップ確認(今やる)
cd ~/code/my-openhl
git checkout main
cargo build --workspace # baseline — L1 前に通るべき
cd ~/code/openhl-reference # answer-key 用の別チェックアウト
git checkout cd94137
12 レッスンのロードマップ
| # | build するもの | 終了時テスト |
|---|---|---|
| 0 | Orientation(本レッスン) | セットアップ確認 |
| 1 | RATE_SCALE = 1e9 — 固定小数点方式 | cargo check -p openhl-funding |
| 2 | 金額 newtype(MarkPrice/IndexPrice/Premium/Notional) | 型がコンパイル |
| 3 | Position/Settlement/FundingParams + HL デフォルト | roster 完成 |
| 4 | compute_premium + 符号対称性テスト | premium テスト pass |
| 5 | saturate_i128_to_i64 + overflow 哲学 + 最初の proptest | proptest pass |
| 6 | compute_rate — divisor + cap + clamp | rate テスト pass |
| 7 | apply_funding — 符号規約 + zero-sum proptest | zero-sum proptest pass |
| 8 | FundingClock + tick() discrete event loop | clock がコンパイル |
| 9 | Interval-gating 不変条件 — 境界テスト 3 つ | gating テスト pass |
| 10 | No-catch-up 不変条件 — 1 テストで設計哲学 | 22 tests pass |
| 11 | Capstone | (recap) |
マイルストーンは レッスン10 — 22 tests が全部通り、deterministic な funding パイプライン(premium→rate→settlement)が no-catch-up clock で駆動する。レッスン11 で「まだ何が足りないか(oracle / liquidation / bridge 接続)」を名指す。
答え合わせの規律
12 レッスンすべてが Funding 参照実装コミット cd94137 を引用する(funding 実装が 1 コミットにまとまっているため)。レッスン11 終了時点の answer-key diff は crates/funding/ 配下で cd94137 に一致する。
cd ~/code/openhl-reference && git checkout cd94137
diff -u ~/code/my-openhl/crates/funding/src/types.rs ./crates/funding/src/types.rs
diff -u ~/code/my-openhl/crates/funding/src/compute.rs ./crates/funding/src/compute.rs
diff -u ~/code/my-openhl/crates/funding/src/clock.rs ./crates/funding/src/clock.rs
diff -u ~/code/my-openhl/crates/funding/src/lib.rs ./crates/funding/src/lib.rs
合格基準
cargo test -p openhl-funding --releaseを 22 tests 通せる(コース完走時)。- なぜ funding に float を使えないか(非決定性 → fork)を 1 文で言える。
- premium → rate → capped → settlement の 4 段を、固定小数点でなぞれる。
まとめ(3行)
- funding 支払いが mark price を index に anchor する — Mark>index なら longs→shorts、Mark<index なら shorts→longs を固定 interval ごとに。
- パイプラインは premium → rate → capped → settlement の 4 段。すべて
RATE_SCALE = 1e9でスケールした符号付き整数で計算 — float は 1 LSB のズレで fork を招くから使わない。 - 純粋な state machine(I/O ゼロ)+ 飽和演算。clock 不変条件 2 つ(interval ごと最多 1 回、catch-up なし)を強制する。