Appearance
L3-3: Options(期权合约) #
1. 问题 #
期权是传统金融中万亿美元级别的衍生品市场,但链上实现面临着独特的挑战。核心问题是:如何在没有中心化清算所的情况下,确保期权卖方一定会履约?在 TradFi 中,期权清算所(OCC)作为中央对手方担保所有交易。在 DeFi 中,智能合约必须通过托管机制来消除对手方风险——卖方提前锁定全额抵押品,买方提前支付权利金。
但这带来了第二个问题:如何公平地计算行权收益?链上没有"市场收盘价",需要依赖预言机提供结算价格。同时,欧式期权只能在到期日行权(与随时可行权的美式期权相对),这需要合约精确地管理时间窗口和状态转换。
2. 原因 #
DeFi 期权协议(如 Opyn、Ribbon、Hegic)正在将万亿美元级别的传统市场引入链上。链上期权的核心优势是消除对手方风险——卖方无法违约,买方无法拒绝支付。理解期权的铸造(write)→购买(buy)→行权(exercise)生命周期是进入 DeFi 衍生品开发的基础。
对开发者而言,期权合约是一个优秀的练习:它涉及状态机设计(Active → Purchased → Exercised)、支付结算逻辑、抵押品管理和时间锁。这些模式在更复杂的结构化产品和永续合约中反复出现。
3. 方案 #
EuropeanOption.sol 实现了完整的欧式期权系统,支持看涨(CALL)和看跌(PUT)两种类型。
状态机设计:Option 结构体维护了四个状态: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 的操纵窗口在 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 等 |