Appearance
L3-5: Order Book(オンチェーンオーダーブック)
1. 問題
オーダーブックは伝統的取引所(NASDAQ、NYSE、Binance)の中核インフラですが、Ethereumメインネット上で直接実装すると深刻なガスの障壁に直面します。活発なオーダーブックでは毎秒数千件の注文、取消、約定が発生します。各操作にL1トランザクションが必要だと、ガスコストにより小口トレーダーは参加できなくなります。AMM(自動マーケットメイカー)はコンスタントプロダクト式によってこの問題を巧妙に回避していますが、AMMは大口取引でスリッページが発生し、価格発見効率もオーダーブックに劣ります。
ガスコストを抑えつつ、オンチェーンで指値オーダーブックを実装するにはどうすればよいでしょうか。中核的な課題は、メイカー(指値注文者)が偽の注文を防ぐためにトークンをロックする必要があること、テイカー(成行注文者)が最良価格の取引相手を見つける必要があること、そして約定プロセスが両当事者の利益を保護するためにアトミックに実行される必要があることです。
2. 理由
L2の成熟に伴い(Base、Arbitrum、Optimismではガス代が$0.01まで低下)、オンチェーンオーダーブックは再び実現可能になりました。AMMと比較して、オーダーブックはより正確な価格制御(指値注文)、より低いスリッページ(特に大口取引)、より柔軟な注文タイプ(ストップロス注文、アイスバーグ注文など)を提供します。オンチェーンオーダーブックの設計パターンを理解することは、中央集権型取引所との連携、アグリゲーターやオンチェーンデリバティブの構築の基礎となります。
オーダーブック+AMMのハイブリッドモデルは業界のトレンドになりつつあります。Uniswap V4は指値注文をサポートするためのフックを導入し、CoWSwapはオフチェーンオーダーブック+オンチェーンバッチ決済を使用しています。オーダーブックパターンを習得することは、次世代DEXアーキテクチャを理解する鍵です。
3. 解決策
OnChainOrderBook.solは部分約定をサポートする指値オーダーブックを実装しています。
データ構造:Order構造体は、maker(注文者)、baseToken/quoteToken(取引ペア)、side(BUY/SELL)、price(価格、精度1e18)、amount(総数量)、filled(約定済み数量)、status(ACTIVE/PARTIALLY_FILLED/FILLED/CANCELLED)を記録します。
注文を出す(Place Order):placeOrder()はmakerによって呼び出されます。売り注文の場合、makerはbaseTokenをロックします。買い注文の場合、makerはquoteTokenをロックします(購入総額 = amount * price / 1e18)。トークンは約定または取消までコントラクトにエスクローされます。
注文を約定する(Fill Order):fillOrder()はtakerによって呼び出され、約定する注文IDと数量を指定します。部分約定をサポートしており、fillAmount < remainingの場合、注文状態はPARTIALLY_FILLEDになります。資産はアトミックに交換されます:
- 買い注文の約定:takerがbaseTokenをmakerに送り、エスクローからquoteTokenを受け取る
- 売り注文の約定:takerがquoteTokenをmakerに送り、エスクローからbaseTokenを受け取る
注文を取消す(Cancel Order):cancelOrder()はmakerによって呼び出され、ACTIVEまたはPARTIALLY_FILLED状態の注文にのみ有効です。未約定部分のエスクロートークンがmakerに返還されます。
参考ソースファイル:src/level3/OnChainOrderBook.sol
4. 遭遇した落とし穴
- 価格優先度の欠如:コントラクト自体は価格-時間優先度を保証せず、誰でも任意の順序で注文を約定できるため、最良価格の注文がスキップされる可能性があります
- 部分約定後の返金計算:注文取消時に未約定数量のトークンが返金されますが、買い注文の返金は比例計算
(unfilled * price) / 1e18が必要であり、整数除算により1 weiの丸め差異が生じる可能性があります - 自己取引のリスク:約定チェックにおいてmaker == takerが明示的に禁止されておらず、無意味な自己取引とガスの浪費が発生する可能性があります
- 注文がフロントランされる可能性:悪意のある行為者がmempool内の約定トランザクションを監視し、テイカーよりも先に同じ価格の約定を提出できます(プライオリティガスオークション)
- オーダーブックにソート機構がない:オンチェーンで価格順に注文をソートする効率的な方法がなく、テイカーは最良価格を見つけるためにすべての注文をオフチェーンでスキャンする必要があります
5. 落とし穴の原因
価格優先度の問題は、オンチェーンオーダーブックの根本的な課題です。中央集権型取引所では、約定エンジンがメモリ上で動作し、ミリ秒単位でオーダーブックをソートしてマッチングできます。オンチェーンでは、すべてのデータがEVM状態に保存され、ネイティブのソートデータ構造(ヒープ、優先度キューなど)がありません。つまり、最良価格の発見はオフチェーンで行う必要があります。オフチェーンサービスがオーダーブックをスキャンし、最良価格の注文を見つけて、ユーザーをオンチェーンでの実行に誘導します。
これにより信頼の前提が導入されます。ユーザーは、オフチェーンサービスが誤った価格や次善の価格を提供しないことを信頼する必要があります。OnChainOrderBook.solでは、fillOrder()は任意の_orderIdを受け入れ、価格検証を一切行いません。実際の本番実装では、テイカーが期待する価格範囲を指定できるようにし、約定される注文の価格が確かにその範囲内であることをコントラクトが検証すべきです。
フロントラン問題はAMMにも存在しますが、オーダーブックではより深刻です。注文が公開されているため、攻撃者はすべての指値注文をスキャンし、収益性の高い約定機会を特定して先に提出できます。緩和策としては、バッチオークションモデルを使用して複数の注文を集約し、同一ブロック内で統一的に決済する方法や、シールド注文を使用してmempoolからの可視性を防ぐ方法があります。
6. 落とし穴の解決方法
オフチェーン約定+オンチェーン検証:オフチェーン約定サービスを構築し、価格順にソートしてユーザーの約定を誘導します。コントラクトに価格範囲検証を追加します:
solidity
function fillOrder(uint256 _orderId, uint256 _fillAmount, uint256 _maxPrice, uint256 _minPrice) external {
Order storage order = orders[_orderId];
require(order.price >= _minPrice && order.price <= _maxPrice, "Price out of bounds");
// ... 以降のロジック
}
自己取引の防止:
solidity
require(order.maker != msg.sender, "Cannot fill own order");
バッチオークション:取引活発なペアについては、定期的なバッチ決済を検討します。一定時間内のすべての約定需要と注文需要を収集し、単一ブロック内で最適なマッチングと清算価格を計算します。CoWSwapのソリューションがこのモデルです。
L2最適化:L2上にオーダーブックをデプロイすることで、秒単位のブロック時間とサブセントレベルのガス代の恩恵を受け、オンチェーンオーダーブックを経済的に実現可能にします。さらに、L2シーケンサーが提供する事前確認(pre-confirmation)を活用して、より良いユーザー体験を実現できます。
7. 技術ポイント
| 技術ポイント | 説明 |
|---|---|
| Maker/Takerモデル | Makerは流動性を提供(注文)、Takerは流動性を消費(約定) |
| エスクローメカニズム | 注文時にトークンをロックし、偽の注文を防止 |
| 部分約定 | amount - filledで残数を追跡 |
| 4状態 | ACTIVE → PARTIALLY_FILLED → FILLED/CANCELLED |
| デュアルトークンペア | baseToken(原資産)+quoteToken(建値) |
| 価格精度 | 1e18スケーリング、UniswapのQ64.96形式に類似 |
| ユーザー追跡 | userOrdersマッピングで各ユーザーの注文リストを管理 |
| 価格発見 | オフチェーンスキャン+オンチェーン検証(本番環境で推奨) |