Appearance
L3-3: Options(オプションコントラクト) #
1. 問題 #
オプションは伝統的金融において数兆ドル規模のデリバティブ市場ですが、オンチェーンでの実装には固有の課題があります。中核的な問題は、中央清算機関なしでいかにオプション売り手の履行を保証するかです。伝統的金融では、オプション清算機関(OCC)が中央取引相手としてすべての取引を保証します。DeFiでは、スマートコントラクトがエスクローメカニズムを通じて取引相手リスクを排除する必要があります。売り手は事前に全額の担保をロックし、買い手は事前にプレミアムを支払います。
しかし、これにより第二の問題が生じます。権利行使の利益をどのように公正に計算するかです。オンチェーンには「市場終値」が存在しないため、決済価格を提供するためにオラクルに依存する必要があります。また、ヨーロピアンオプションは満期日にのみ権利行使可能であり(いつでも行使可能なアメリカンオプションとは対照的)、コントラクトが時間枠と状態遷移を正確に管理する必要があります。
2. 理由 #
DeFiオプションプロトコル(Opyn、Ribbon、Hegicなど)は、数兆ドル規模の伝統的市場をオンチェーンに導入しつつあります。オンチェーンオプションの中核的利点は取引相手リスクの排除です。売り手はデフォルトできず、買い手は支払いを拒否できません。オプションのライフサイクルである、ライティング(write)→ 購入(buy)→ 権利行使(exercise)を理解することは、DeFiデリバティブ開発に入るための基礎です。
開発者にとって、オプションコントラクトは優れた演習題材です。状態機械設計(Active → Purchased → Exercised)、支払決済ロジック、担保管理、タイムロックなどが含まれます。これらのパターンは、より複雑な仕組商品やパーペチュアルコントラクトでも繰り返し登場します。
3. 解決策 #
EuropeanOption.solは、コール(CALL)とプット(PUT)の両タイプをサポートする完全なヨーロピアンオプションシステムを実装しています。
状態機械設計:Option構造体は4つの状態を保持します。ACTIVE(売り手が担保をロック済み、購入待ち)、PURCHASED(買い手がプレミアムを支払い済み、満期と権利行使待ち)、EXERCISED(権利行使・決済済み)、EXPIRED(予約されているが未使用)です。
オプションの作成(Write):createOption()は売り手によって呼び出され、オプションタイプ、権利行使価格、数量、プレミアム、満期時間を指定します。売り手は原資産(amount)を直ちに担保としてコントラクトに転送する必要があります。担保は権利行使時に買い手への支払いに使用されます。
オプションの購入(Buy):買い手がpurchaseOption()を呼び出して売り手にプレミアムを支払います。購入後、オプションの状態はACTIVEからPURCHASEDに変わり、買い手のアドレスがholderとして記録されます。購入は満期前に完了する必要があります。
権利行使(Exercise):満期後に買い手のみが呼び出せます。exercise()は決済価格(_settlementPrice、実際の運用ではオラクルから取得)を受け取り、利益を計算します:
- コールオプション(CALL):ITM条件 =
settlementPrice > strikePrice、利益 =(settlementPrice - strikePrice) * amount / settlementPrice - プットオプション(PUT):ITM条件 =
settlementPrice < strikePrice、利益 =(strikePrice - settlementPrice) * amount / strikePrice
オプションがアウトオブザマネー(OTM)の場合、利益は0です。残りの担保は売り手に返還されます。
参考ソースファイル:src/level3/EuropeanOption.sol
4. 遭遇した落とし穴 #
- 決済価格の操作:
exercise()の_settlementPriceは呼び出し元が直接渡すため、悪意のある呼び出し元が自分に有利な価格を渡す可能性があります - 最大損失をカバーできない担保:コールオプションの売り手の最大損失は理論上無制限ですが(原資産価格は無限に上昇しうる)、コントラクトにロックされた担保は固定の
amountです - 不正確な満期時間検証:
block.timestampを比較に使用する場合、マイナーが小さな範囲でブロックタイムスタンプを操作できます - 購入後のプレミアム返還不可:オプションが満期前に深くアウトオブザマネーになっても、買い手は満期前に売却または放棄できません
- プレミアムがトークン払いだが金額がゼロになりうる:最低額の強制がなく、売り手がゼロプレミアムのオプションを作成し、フロントランされる可能性があります
5. 落とし穴の原因 #
決済価格の操作は、オンチェーンオプションの最も重要なセキュリティ問題です。EuropeanOption.solの現在の実装では、_settlementPriceがexercise()関数にパラメータとして直接渡されます。これはオラクル統合を省略した簡略化によるものです。実際の本番環境では、決済価格は分散型オラクル(Chainlinkなど)によって提供され、onlyOracle修飾子で保護されるべきです。
しかし、Chainlinkを使用する場合でも潜在的な操作リスクがあります。満期の瞬間に、攻撃者がフラッシュローンを使って瞬間的にプール価格を操作し、Chainlinkの価格情報に影響を与える可能性があります(特に流動性の低い資産の場合)。これには、瞬間価格ではなくTWAP(時間加重平均価格)を使用するなど、プロトコルレベルでの保護が必要です。
担保が最大損失をカバーできない問題については、コールオプションの利益計算式(S - K) * amount / Sは、Sが無限大に近づくにつれてamountに近づきます。つまり、amountが固定であれば、売り手の最大損失は担保全額です。プットオプションの場合、利益(K - S) * amount / Kの最大値はS=0のときにamountです。したがって、コントラクト内の担保ロック量は最大可能利益を正確にカバーしており、これは正しい設計です。
6. 落とし穴の解決方法 #
決済価格の安全性:本番環境では、settlementPriceをオラクルクエリに置き換えます:
solidity
function exercise(uint256 _optionId) external nonReentrant {
// Chainlinkオラクルを使用して決済価格を取得
uint256 settlementPrice = oracle.latestAnswer();
require(block.timestamp >= option.expiry, "Not expired");
// ... 以降のロジックは変更なし
}
また、満期後に小さな待機枠(例:1時間)を導入し、オラクル更新中の権利行使を防止することを推奨します。
プレミアムの下限:createOptionに最小プレミアムチェックを追加し、ゼロ手数料オプションのスパム作成を防止します。
満期時間の安全性:block.timestamp > option.expiryではなくblock.timestamp >= option.expiryを使用し、満期時刻での正確な権利行使を許可します。block.timestampの操作可能範囲は30秒以内であり、日単位のオプション満期に対しては無視できる影響です。
7. 技術ポイント #
| 技術ポイント | 説明 |
|---|---|
| ヨーロピアン vs アメリカン | ヨーロピアンは満期日のみ行使可能、アメリカンは満期前いつでも可能 |
| 状態機械 | ACTIVE → PURCHASED → EXERCISED |
| 担保エスクロー | 売り手がamount分のトークンをコントラクトにロック |
| CALL権利行使利益 | max(settlementPrice - strike, 0) * amount / settlementPrice |
| PUT権利行使利益 | max(strike - settlementPrice, 0) * amount / strike |
| 担保返還 | 権利行使後に残りの担保を売り手に返還 |
| ReentrancyGuard | 購入と権利行使にnonReentrantを使用 |
| カスタムエラー | NotWriter、NotHolder、OptionExpiredなど |